您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
One click to send YouTube video url to 3D YouTube Downloader.
当前为
// ==UserScript== // @name 3D Youtube Downloader Helper // @namespace https://riophae.com/ // @version 0.1.3 // @description One click to send YouTube video url to 3D YouTube Downloader. // @author Riophae Lee // @match https://www.youtube.com/* // @run-at document-start // @grant none // ==/UserScript== (function () { 'use strict'; // Types inspired by // https://github.com/Microsoft/TypeScript/blob/9d3707d/src/lib/dom.generated.d.ts#L10581 // Type predicate for TypeScript function isQueryable(object) { return typeof object.querySelectorAll === 'function'; } function select(selectors, baseElement) { // Shortcut with specified-but-null baseElement if (arguments.length === 2 && !baseElement) { return null; } return (baseElement || document).querySelector(selectors); } function selectLast(selectors, baseElement) { // Shortcut with specified-but-null baseElement if (arguments.length === 2 && !baseElement) { return null; } const all = (baseElement || document).querySelectorAll(selectors); return all[all.length - 1]; } /** * @param selectors One or more CSS selectors separated by commas * @param [baseElement] The element to look inside of * @return Whether it's been found */ function selectExists(selectors, baseElement) { if (arguments.length === 2) { return Boolean(select(selectors, baseElement)); } return Boolean(select(selectors)); } function selectAll(selectors, baseElements) { // Shortcut with specified-but-null baseElements if (arguments.length === 2 && !baseElements) { return []; } // Can be: select.all('selectors') or select.all('selectors', singleElementOrDocument) if (!baseElements || isQueryable(baseElements)) { const elements = (baseElements || document).querySelectorAll(selectors); return Array.apply(null, elements); } const all = []; for (let i = 0; i < baseElements.length; i++) { const current = baseElements[i].querySelectorAll(selectors); for (let ii = 0; ii < current.length; ii++) { all.push(current[ii]); } } // Preserves IE11 support and performs 3x better than `...all` in Safari const arr = []; all.forEach(function (v) { arr.push(v); }); return arr; } select.last = selectLast; select.exists = selectExists; select.all = selectAll; var selectDom = select; var global$1 = (typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); // from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js var performance = global$1.performance || {}; var performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function(){ return (new Date()).getTime() }; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var manyKeysMap = createCommonjsModule(function (module) { const getInternalKeys = Symbol('getInternalKeys'); const getPrivateKey = Symbol('getPrivateKey'); const publicKeys = Symbol('publicKeys'); const objectHashes = Symbol('objectHashes'); const symbolHashes = Symbol('symbolHashes'); const nullKey = Symbol('null'); // `objectHashes` key for null let keyCounter = 0; function checkKeys(keys) { if (!Array.isArray(keys)) { throw new TypeError('The keys parameter must be an array'); } } module.exports = class ManyKeysMap extends Map { constructor() { super(); this[objectHashes] = new WeakMap(); this[symbolHashes] = new Map(); // https://github.com/tc39/ecma262/issues/1194 this[publicKeys] = new Map(); // eslint-disable-next-line prefer-rest-params const [pairs] = arguments; // Map compat if (pairs === null || pairs === undefined) { return; } if (typeof pairs[Symbol.iterator] !== 'function') { throw new TypeError(typeof pairs + ' is not iterable (cannot read property Symbol(Symbol.iterator))'); } for (const [keys, value] of pairs) { this.set(keys, value); } } [getInternalKeys](keys, create = false) { const privateKey = this[getPrivateKey](keys, create); let publicKey; if (privateKey && this[publicKeys].has(privateKey)) { publicKey = this[publicKeys].get(privateKey); } else if (create) { publicKey = [...keys]; // Regenerate keys array to avoid external interaction this[publicKeys].set(privateKey, publicKey); } return {privateKey, publicKey}; } [getPrivateKey](keys, create = false) { const privateKeys = []; for (let key of keys) { if (key === null) { key = nullKey; } const hashes = typeof key === 'object' || typeof key === 'function' ? objectHashes : typeof key === 'symbol' ? symbolHashes : false; if (!hashes) { privateKeys.push(key); } else if (this[hashes].has(key)) { privateKeys.push(this[hashes].get(key)); } else if (create) { const privateKey = `@@mkm-ref-${keyCounter++}@@`; this[hashes].set(key, privateKey); privateKeys.push(privateKey); } else { return false; } } return JSON.stringify(privateKeys); } set(keys, value) { checkKeys(keys); const {publicKey} = this[getInternalKeys](keys, true); return super.set(publicKey, value); } get(keys) { checkKeys(keys); const {publicKey} = this[getInternalKeys](keys); return super.get(publicKey); } has(keys) { checkKeys(keys); const {publicKey} = this[getInternalKeys](keys); return super.has(publicKey); } delete(keys) { checkKeys(keys); const {publicKey, privateKey} = this[getInternalKeys](keys); return Boolean(publicKey && super.delete(publicKey) && this[publicKeys].delete(privateKey)); } clear() { super.clear(); this[symbolHashes].clear(); this[publicKeys].clear(); } get [Symbol.toStringTag]() { return 'ManyKeysMap'; } get size() { return super.size; } }; }); const pDefer = () => { const deferred = {}; deferred.promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; }; var pDefer_1 = pDefer; const cache = new manyKeysMap(); const isDomReady = () => document.readyState === 'interactive' || document.readyState === 'complete'; const elementReady = (selector, { target = document, stopOnDomReady = true, timeout = Infinity } = {}) => { const cacheKeys = [target, selector, stopOnDomReady, timeout]; const cachedPromise = cache.get(cacheKeys); if (cachedPromise) { return cachedPromise; } let rafId; const deferred = pDefer_1(); const {promise} = deferred; cache.set(cacheKeys, promise); const stop = () => { cancelAnimationFrame(rafId); cache.delete(cacheKeys, promise); deferred.resolve(); }; if (timeout !== Infinity) { setTimeout(stop, timeout); } // Interval to keep checking for it to come into the DOM. (function check() { const element = target.querySelector(selector); if (element) { deferred.resolve(element); stop(); } else if (stopOnDomReady && isDomReady()) { stop(); } else { rafId = requestAnimationFrame(check); } })(); return Object.assign(promise, {stop}); }; var elementReady_1 = elementReady; const FALLBACK_LANG = 'en-US'; const ID_SUFFIX = '3d-youtube-downloader-helper'; let isMenuOpen = false; let isTooltipShown = false; let justOpenedMenu = false; function memoize(fn) { let value; return () => { if (fn) { value = fn(); if (value != null) { fn = null; } } return value } } const isWindowsOS = () => navigator.platform === 'Win32'; const isEmbeddedVideo = () => window.location.pathname.startsWith('/embed/'); const getLang = () => document.documentElement.getAttribute('lang'); const getVideoId = () => isEmbeddedVideo() // eslint-disable-line no-confusing-arrow ? window.location.pathname.split('/').pop() : selectDom('[video-id]').getAttribute('video-id'); const getButton = memoize(() => selectDom(`#button-${ID_SUFFIX}`)); const getTooltip = memoize(() => selectDom(`#tooltip-${ID_SUFFIX}`)); const getMenu = memoize(() => selectDom(`#menu-${ID_SUFFIX}`)); const getInnerMenu = memoize(() => selectDom(`#inner-menu-${ID_SUFFIX}`)); const getDownloadLink = memoize(() => selectDom(`#download-link-${ID_SUFFIX}`)); const getConvertLink = memoize(() => selectDom(`#convert-link-${ID_SUFFIX}`)); const getAnalyzeLink = memoize(() => selectDom(`#analyze-link-${ID_SUFFIX}`)); const dict = { 'en-US': { buttonTitle: 'Download via 3D YouTube Downloader', download: 'Download', convert: 'Convert', analyze: 'Analyze', }, 'zh-CN': { buttonTitle: '通过 3D YouTube Downloader 下载', download: '下载', convert: '转换', analyze: '分析', }, }; dict.zh = dict['zh-CN']; function i18n(key) { let lang = getLang(); // eslint-disable-next-line no-prototype-builtins if (!dict.hasOwnProperty(lang)) { lang = FALLBACK_LANG; } const translated = dict[lang][key] || dict[FALLBACK_LANG][key]; return translated } function insertControls(youtubeSettingsMenu, youtubeRightControls) { const createMenuItem = key => ` <a id="${key}-link-${ID_SUFFIX}" class="ytp-menuitem" tabindex="0"> <div class="ytp-menuitem-label" style="white-space: nowrap">${i18n(key)}</div> <div class="ytp-menuitem-content"></div> </a> `; const buttonHtml = ` <button id="button-${ID_SUFFIX}" class="ytp-button"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 459 459" style="transform: scale(0.45)"> <path fill="#FFF" d="M446.25 56.1l-35.7-43.35C405.45 5.1 395.25 0 382.5 0h-306C63.75 0 53.55 5.1 45.9 12.75L12.75 56.1C5.1 66.3 0 76.5 0 89.25V408c0 28.05 22.95 51 51 51h357c28.05 0 51-22.95 51-51V89.25c0-12.75-5.1-22.95-12.75-33.15zM229.5 369.75L89.25 229.5h89.25v-51h102v51h89.25L229.5 369.75zM53.55 51l20.4-25.5h306L402.9 51H53.55z"/> </svg> </button> `; const tooltipHtml = ` <div id="tooltip-${ID_SUFFIX}" class="ytp-tooltip ytp-bottom" style="opacity: 0"> <div class="ytp-tooltip-bg"> <div class="ytp-tooltip-duration"></div> </div> <div class="ytp-tooltip-text-wrapper"> <div class="ytp-tooltip-image"></div> <div class="ytp-tooltip-title"></div> <span class="ytp-tooltip-text">${i18n('buttonTitle')}</span> </div> </div> `; const menuHtml = ` <div id="menu-${ID_SUFFIX}" class="ytp-popup ytp-settings-menu" style="display: none"> <div class="ytp-panel"> <div id="inner-menu-${ID_SUFFIX}" class="ytp-panel-menu" style="min-width: 8em" role="menu"> ${createMenuItem('download')} ${createMenuItem('convert')} ${createMenuItem('analyze')} </div> </div> </div> `; youtubeSettingsMenu.insertAdjacentHTML('beforebegin', menuHtml); youtubeSettingsMenu.insertAdjacentHTML('beforebegin', tooltipHtml); youtubeRightControls.insertAdjacentHTML('afterbegin', buttonHtml); } function adjustPosition(element) { element.style.right = '0'; const elementRect = element.getBoundingClientRect(); const buttonRect = getButton().getBoundingClientRect(); const youtubeSettingsMenuStyle = getComputedStyle(selectDom('.ytp-settings-menu[id^="ytp-"]')); const elementCenterX = elementRect.x + elementRect.width / 2; const buttonCenterX = buttonRect.x + buttonRect.width / 2; const diff = elementCenterX - buttonCenterX; const youtubeSettingsMenuRight = parseInt(youtubeSettingsMenuStyle.right, 10); element.style.right = Math.max(diff, youtubeSettingsMenuRight) + 'px'; } function showTooltip() { if (isTooltipShown) return isTooltipShown = true; getTooltip().style.opacity = '1'; adjustPosition(getTooltip()); getMenu().style.display = ''; getTooltip().style.bottom = getComputedStyle(getMenu()).bottom; getMenu().style.display = 'none'; } function hideTooltip() { if (!isTooltipShown) return isTooltipShown = false; getTooltip().style.opacity = '0'; } function setDownloadUrls() { const videoId = getVideoId(); const videoUrl = `https://www.youtube.com/watch?v=${videoId}`; getDownloadLink().href = `s3dyd://download=${videoUrl}`; getConvertLink().href = `s3dyd://convert=${videoUrl}`; getAnalyzeLink().href = `s3dyd://analyze=${videoUrl}`; } function setMenuSize(width, height) { width += 'px'; height += 'px'; Object.assign(getInnerMenu().parentElement.style, { width, height }); Object.assign(getMenu().style, { width, height }); } function showMenu() { if (isMenuOpen) return isMenuOpen = true; getMenu().style.opacity = '1'; getMenu().style.display = ''; const { offsetWidth: width, offsetHeight: height } = getInnerMenu(); setMenuSize(width, height); setDownloadUrls(); adjustMenuPosition(); } function adjustMenuPosition() { adjustPosition(getMenu()); } function hideMenu() { if (!isMenuOpen) return isMenuOpen = false; getMenu().style.opacity = '0'; getMenu().addEventListener( 'transitionend', event => { if (event.propertyName === 'opacity' && getMenu().style.opacity === '0') { getMenu().style.display = 'none'; getMenu().style.opacity = ''; } }, { once: true }, ); } function bindEventHandlers() { getButton().addEventListener('click', () => { if (isMenuOpen) { return } justOpenedMenu = true; hideTooltip(); showMenu(); }); getButton().addEventListener('contextmenu', event => { event.preventDefault(); event.stopPropagation(); hideTooltip(); hideMenu(); setDownloadUrls(); getDownloadLink().click(); }); getButton().addEventListener('mouseenter', () => { if (!isMenuOpen) { showTooltip(); } }); getButton().addEventListener('mouseleave', () => { if (!isMenuOpen) { hideTooltip(); } }); window.addEventListener('click', () => { if (isMenuOpen && !justOpenedMenu) { hideMenu(); } justOpenedMenu = false; }); window.addEventListener('blur', () => { if (isMenuOpen) { hideMenu(); } }); } async function init() { if (!isWindowsOS()) { return } const [ youtubeSettingsMenu, youtubeRightControls ] = await Promise.all([ elementReady_1('.ytp-settings-menu'), elementReady_1('.ytp-right-controls'), ]); if (youtubeSettingsMenu && youtubeRightControls) { insertControls(youtubeSettingsMenu, youtubeRightControls); bindEventHandlers(); } } init(); }());
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址