您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Video/short download button hidden in three dots combo menu below video or next to subscribe button. Downloads MP4, WEBM or MP3 from youtube + option to redirect shorts to normal videos. Choose your preferred quality from 8k to audio only, codec (h264, vp9 or av1) or service provider (cobalt, y2mate, yt1s) in settings.
当前为
- // ==UserScript==
- // @name YouTube Direct Downloader
- // @version 2.4.1
- // @description Video/short download button hidden in three dots combo menu below video or next to subscribe button. Downloads MP4, WEBM or MP3 from youtube + option to redirect shorts to normal videos. Choose your preferred quality from 8k to audio only, codec (h264, vp9 or av1) or service provider (cobalt, y2mate, yt1s) in settings.
- // @author FawayTT
- // @namespace FawayTT
- // @supportURL https://github.com/FawayTT/userscripts/issues
- // @icon https://github.com/FawayTT/userscripts/blob/main/ydd-icon.png?raw=true
- // @match https://www.youtube.com/*
- // @connect api.cobalt.tools
- // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_registerMenuCommand
- // @grant GM_openInTab
- // @grant GM_xmlhttpRequest
- // @license MIT
- // ==/UserScript==
- const gmcCSS = `
- #YDD_config {
- background-color: rgba(0, 0, 0, 0.8) !important;
- backdrop-filter: blur(10px);
- color: #fff !important;
- border-radius: 30px !important;
- padding: 20px !important;
- height: fit-content !important;
- max-width: 700px !important;
- font-family: Arial, sans-serif !important;
- z-index: 9999999 !important;
- padding-bottom: 0px !important;
- width: 100% !important;
- }
- #YDD_config_header {
- background-color: #ff000052 !important;
- border-radius: 10px;
- padding: 10px !important;
- text-align: center !important;
- font-size: 24px !important;
- color: blob !important;
- font-weight: 600 !important;
- }
- .section_header_holder {
- font-weight: 600;
- margin-top: 10px !important;
- }
- #YDD_config_buttons_holder {
- text-align: center;
- margin-top: 20px;
- }
- #YDD_config_resetLink {
- color: #fff !important;
- }
- .config_var {
- margin: 0px !important;
- line-height: 3;
- }
- #YDD_config_buttons_holder button {
- background-color: #ff000052 !important;
- color: #fff;
- border: none;
- font-weight: 600;
- padding: 10px 20px !important;
- border-radius: 10px;
- font-size: 14px;
- cursor: pointer;
- transition: background-color 0.1s ease-in;
- }
- #YDD_config_buttons_holder button:hover {
- background-color: #ff0000 !important;
- }
- #YDD_config_fieldset {
- border: 1px solid #444;
- padding: 10px;
- border-radius: 5px;
- margin-top: 10px;
- }
- #YDD_config_fieldset legend {
- color: #ff0000;
- }
- .section_header {
- background: none !important;
- width: fit-content;
- margin: 5px 0px !important;
- border: none !important;
- font-size: 18px !important;
- color: #ff0000 !important;
- }
- input, select, textarea {
- cursor: pointer;
- background-color: #333;
- color: #fff;
- border: 1px solid #555;
- border-radius: 10px;
- padding: 5px;
- margin: 5px 0 !important;
- }
- ::selection {
- color: white;
- background: #ff0000;
- }
- input, select, textarea {
- transition: all 0.1s ease-in;
- }
- input:focus, select:focus, textarea:focus {
- border-color: #ff0000;
- }
- input:hover, select:hover, textarea:hover {
- opacity: 0.8;
- }
- label {
- color: #fff;
- }
- #YDD_config_buttons_holder {
- position: relative;
- margin-top: 0px !important;
- display: flex;
- justify-content: center;
- align-items: baseline;
- }
- .reset_holder {
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- right: 0;
- padding: 10px;
- }
- input[type='checkbox'] {
- appearance: none;
- position: absolute;
- width: 20px;
- transform: translateY(3px);
- height: 20px;
- border: 1px solid #555;
- border-radius: 10px;
- background-color: #333;
- cursor: pointer;
- }
- input[type='checkbox']:before {
- content: " ";
- }
- input[type='checkbox']:checked {
- background-color: #d50707;
- }
- input[type='checkbox']:checked::after {
- content: "";
- position: absolute;
- top: 3px;
- left: 2px;
- width: 12px;
- height: 6px;
- border-bottom: 2px solid #ffffff;
- border-left: 2px solid #ffffff;
- transform: rotate(-45deg);
- }
- #YDD_config_downloadService_var:after {
- content: "▲ Use cobalt for best quality.";
- display: block;
- font-family: arial, tahoma, myriad pro, sans-serif;
- font-size: 10px;
- font-weight: bold;
- margin-right: 6px;
- opacity: 0.7;
- }
- #YDD_config_vCodec_var:after {
- content: "▲ H264 [MP4] = best compatibility. VP9 [WEBM] = better quality. AV1 = best quality but is used only by few videos.";
- display: block;
- font-family: arial, tahoma, myriad pro, sans-serif;
- font-size: 10px;
- font-weight: bold;
- margin-right: 6px;
- opacity: 0.7;
- }
- #YDD_config_backupService_var:after {
- content: "▲ In case Cobalt isn't working, automatically use this download service.";
- display: block;
- font-family: arial, tahoma, myriad pro, sans-serif;
- font-size: 10px;
- font-weight: bold;
- margin-right: 6px;
- opacity: 0.7;
- }
- `;
- const yddCSS = `
- ydd-item {
- cursor: pointer;
- margin-top: 8px;
- font-size: 1.4rem;
- line-height: 2rem;
- font-weight: 400;
- position: relative;
- color: var(--yt-spec-text-primary);
- font-family: "Roboto","Arial",sans-serif;
- white-space: nowrap;
- display: flex;
- margin-bottom: -10px;
- padding: 10px 0 10px 21px;
- gap: 23px;
- align-items: center;
- text-transform: capitalize;
- }
- ydd-item:hover {
- background-color: var(--yt-spec-10-percent-layer);
- }
- ydd-item-icon {
- content: '⇩';
- font-size: 2.1rem;
- }
- ydd-item-text {
- position: relative;
- }
- ydd-item-button {
- position: absolute;
- left: 0;
- top: 0;
- width: 90%;
- height: 100%;
- opacity: 0;
- cursor: pointer;
- z-index: 9999;
- }
- ydd-item-sidebar {
- position: absolute;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 3px;
- padding: 1px;
- color: var(--yt-spec-text-primary);
- z-index: 9999;
- right: 0;
- top: 0;
- width: 10%;
- height: 90%;
- }
- #ydd-button-sub {
- cursor: pointer;
- font-size: 2rem;
- padding: 8px 12px;
- border: none;
- border-radius: 15px;
- margin-left: 8px;
- line-height: 2rem;
- font-weight: 500;
- color: #0f0f0f;
- background-color: #f1f1f1;
- font-family: "Roboto","Arial",sans-serif;
- align-items: center;
- text-transform: capitalize;
- }
- #ydd-button-sub:hover {
- filter: brightness(90%);
- }
- ytd-menu-popup-renderer {
- min-height: 100px !important;
- min-width: 133px !important;
- }
- `;
- GM_registerMenuCommand('Settings', opencfg);
- const defaults = {
- downloadService: 'auto',
- quality: 'max',
- vCodec: 'vp9',
- aFormat: 'mp3',
- filenamePattern: 'pretty',
- buttonDownloadInfo: 'onchange',
- isAudioMuted: false,
- disableMetadata: false,
- redirectShorts: false,
- backupService: 'y2mate',
- subscribeButton: true,
- };
- let frame = document.createElement('div');
- document.body.appendChild(frame);
- let gmc = new GM_config({
- id: 'YDD_config',
- title: 'YouTube Direct Downloader (YDD) - Settings',
- css: gmcCSS,
- frame: frame,
- fields: {
- subscribeButton: {
- section: ['Position of download button'],
- label: 'Show download button next to subscribe button:',
- labelPos: 'left',
- type: 'checkbox',
- default: defaults.subscribeButton,
- },
- downloadService: {
- section: ['Download method'],
- label: 'Service:',
- labelPos: 'left',
- type: 'select',
- default: defaults.downloadService,
- options: ['auto', 'cobalt', 'y2mate', 'yt1s'],
- },
- quality: {
- section: ['Cobalt-only settings'],
- label: 'Quality:',
- labelPos: 'left',
- type: 'select',
- default: defaults.quality,
- options: ['max', '2160', '1440', '1080', '720', '480', '360', '240', '144'],
- },
- vCodec: {
- label: 'Video codec:',
- labelPos: 'left',
- type: 'select',
- default: defaults.vCodec,
- options: ['h264', 'vp9', 'av1'],
- },
- aFormat: {
- label: 'Audio format:',
- type: 'select',
- default: defaults.aFormat,
- options: ['best', 'mp3', 'ogg', 'wav', 'opus'],
- },
- isAudioMuted: {
- label: 'Download videos without audio:',
- type: 'checkbox',
- default: defaults.isAudioMuted,
- },
- disableMetadata: {
- label: 'Download videos without metadata:',
- type: 'checkbox',
- default: defaults.disableMetadata,
- },
- filenamePattern: {
- label: 'Filename pattern:',
- type: 'select',
- default: defaults.filenamePattern,
- options: ['classic', 'pretty', 'basic', 'nerdy'],
- },
- buttonDownloadInfo: {
- label: 'Show quality info below button:',
- type: 'select',
- default: defaults.buttonDownloadInfo,
- options: ['always', 'onchange', 'never'],
- },
- backupService: {
- label: 'Backup service:',
- type: 'select',
- default: defaults.backupService,
- options: ['y2mate', 'yt1s', 'none'],
- },
- redirectShorts: {
- section: ['Extra features'],
- label: 'Redirect shorts:',
- labelPos: 'left',
- type: 'checkbox',
- default: defaults.redirectShorts,
- },
- url: {
- section: ['Links'],
- label: 'All my userscripts - FawayTT',
- type: 'button',
- click: () => {
- GM_openInTab('https://github.com/FawayTT/userscripts');
- },
- },
- cobaltUrl: {
- label: 'Cobalt github page',
- type: 'button',
- click: () => {
- GM_openInTab('https://github.com/imputnet/cobalt');
- },
- },
- },
- events: {
- save: function () {
- gmc.close();
- deleteButton();
- createButton();
- deleteSubscribeButton();
- createSubscribeButton();
- },
- init: onInit,
- },
- });
- function opencfg() {
- gmc.open();
- }
- let menuOuter;
- let menuParent;
- let nextSibling;
- let oldHref = document.location.href;
- let yddAdded = false;
- function getYouTubeVideoID(url) {
- if (url.includes('shorts')) {
- const regex = /\/shorts\/([^/?]+)/;
- const match = url.match(regex);
- const id = match ? match[1] : null;
- return id;
- }
- const urlParams = new URLSearchParams(new URL(url).search);
- return urlParams.get('v');
- }
- function handleCobaltError(errorMessage, isAudioOnly) {
- const backupService = gmc.get('backupService') || 'y2mate';
- if (gmc.get('downloadService') === 'auto') {
- download(isAudioOnly, 'y2mate');
- return;
- }
- let alertText = 'Cobalt error: ' + (errorMessage || 'Something went wrong! Try again later.');
- if (backupService !== 'none') {
- alertText += '\n\nYou will be redirected to backup provider ' + backupService + '.';
- alert(alertText);
- download(isAudioOnly, backupService);
- } else alert(alertText);
- }
- function download(isAudioOnly, downloadService) {
- if (!downloadService) downloadService = gmc.get('downloadService');
- switch (downloadService) {
- case 'y2mate':
- if (isAudioOnly) window.open(`https://www.y2mate.com/youtube-mp3/${getYouTubeVideoID(document.location.href)}`);
- else window.open(`https://www.y2mate.com/download-youtube/${getYouTubeVideoID(document.location.href)}`);
- break;
- case 'yt1s':
- if (isAudioOnly) window.open(`https://www.yt1s.com/en/youtube-to-mp3?q=${getYouTubeVideoID(document.location.href)}`);
- else window.open(`https://www.yt1s.com/en/youtube-to-mp4?q=${getYouTubeVideoID(document.location.href)}`);
- break;
- default:
- GM_xmlhttpRequest({
- method: 'POST',
- url: 'https://api.cobalt.tools/api/json',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- },
- data: JSON.stringify({
- url: encodeURI(document.location.href),
- vQuality: gmc.get('quality'),
- vCodec: gmc.get('vCodec'),
- aFormat: gmc.get('aFormat'),
- filenamePattern: gmc.get('filenamePattern'),
- isAudioMuted: gmc.get('isAudioMuted'),
- disableMetadata: gmc.get('disableMetadata'),
- isAudioOnly: isAudioOnly,
- }),
- onload: (response) => {
- try {
- const data = JSON.parse(response.responseText);
- if (data.url) window.open(data.url);
- else handleCobaltError(data.text, isAudioOnly);
- } catch (error) {
- handleCobaltError(null, isAudioOnly);
- if (downloadService !== 'auto') console.error(error);
- }
- },
- onerror: function (error) {
- const errorMessage = error.message || error;
- handleCobaltError(errorMessage, isAudioOnly);
- },
- ontimeout: function () {
- const alertText = 'Cobalt is not responding. Please try again later.';
- handleCobaltError(alertText, isAudioOnly);
- },
- });
- break;
- }
- hideMenu();
- }
- function addButtonDownloadInfo(serviceName, div) {
- if (serviceName === 'cobalt') {
- const option = gmc.get('buttonDownloadInfo');
- if (option === 'never') return;
- const quality = gmc.get('quality') || defaults.quality;
- const vCodec = gmc.get('vCodec') || defaults.vCodec;
- if (option === 'onchange' && quality === defaults.quality && vCodec === defaults.vCodec) return;
- const qualityText = `${quality}, ${vCodec}`;
- const downloadInfo = document.createElement('ydd-item-button-info');
- downloadInfo.style.cssText = `
- position: absolute;
- left: 0;
- bottom: -10px;
- font-size: 0.8rem;
- color: var(--yt-spec-text-primary);
- opacity: 0.6;`;
- downloadInfo.innerText = qualityText;
- div.appendChild(downloadInfo);
- }
- }
- function deleteButton() {
- const buttons = document.getElementsByTagName('ydd-item');
- if (buttons.length === 0) return;
- const button = buttons[0];
- button.remove();
- }
- function hideMenu() {
- const menu = document.getElementsByTagName('ytd-menu-popup-renderer')[0];
- if (!menu) return;
- menuOuter = menu.parentElement.parentElement;
- if (!menuOuter) return;
- menuParent = menuOuter.parentNode;
- nextSibling = menuOuter.nextSibling;
- menuOuter.remove();
- }
- function addMenu() {
- if (menuOuter && menuParent) {
- if (nextSibling) {
- menuParent.insertBefore(menuOuter, nextSibling); // Re-insert before the next sibling if it exists
- } else {
- menuParent.appendChild(menuOuter); // Append if it's the last child
- }
- menuOuter.style.display = 'none';
- }
- }
- const addStyles = () => {
- const style = document.createElement('style');
- style.type = 'text/css';
- style.innerHTML = yddCSS;
- document.head.appendChild(style);
- };
- function createButton() {
- addMenu();
- if (document.getElementsByTagName('ydd-item').length !== 0) return;
- const serviceName = gmc.get('downloadService') || defaults.downloadService;
- const menu = document.getElementsByTagName('ytd-menu-popup-renderer')[0];
- const item = document.createElement('ydd-item');
- const icon = document.createElement('ydd-item-icon');
- const text = document.createElement('ydd-item-text');
- const button = document.createElement('ydd-item-button');
- const sidebar = document.createElement('ydd-item-sidebar');
- const settings = document.createElement('ydd-item-settings');
- const audioButton = document.createElement('ydd-item-audio-download');
- text.innerText = serviceName === 'auto' ? 'YDD' : serviceName;
- icon.innerText = '⇩';
- settings.innerText = '☰';
- audioButton.innerText = '▶';
- addButtonDownloadInfo(serviceName, text);
- audioButton.title = `Download audio only`;
- settings.title = 'Settings';
- item.appendChild(icon);
- item.appendChild(text);
- item.appendChild(button);
- item.appendChild(sidebar);
- sidebar.appendChild(settings);
- sidebar.appendChild(audioButton);
- button.addEventListener('click', () => {
- download();
- });
- audioButton.addEventListener('click', () => {
- download(true);
- });
- settings.addEventListener('click', opencfg);
- menu.insertBefore(item, menu.firstChild);
- }
- function deleteSubscribeButton() {
- const button = document.getElementById('ydd-button-sub');
- if (!button) return;
- button.remove();
- }
- function createSubscribeButton() {
- if (!gmc.get('subscribeButton')) return;
- const ownerBar = document.getElementById('owner');
- if (!ownerBar || document.getElementById('ydd-button-sub')) return;
- const button = document.createElement('button');
- button.id = 'ydd-button-sub';
- ownerBar.appendChild(button);
- let downloadService = gmc.get('downloadService') || defaults.downloadService;
- if (downloadService === 'auto') {
- button.title = 'Download with YDD';
- } else button.title = 'Download via ' + downloadService;
- button.innerText = '⇩';
- button.addEventListener('click', () => {
- download();
- });
- }
- function checkShort() {
- if (document.location.href.indexOf('youtube.com/shorts') > -1) {
- if (gmc.get('redirectShorts')) window.location.replace(window.location.toString().replace('/shorts/', '/watch?v='));
- return true;
- } else return false;
- }
- function modifyMenu() {
- const short = checkShort();
- if (document.location.href.indexOf('youtube.com/watch') === -1 && !short) {
- yddAdded = true;
- return;
- }
- addMenu();
- menuOuter = null;
- menuParent = null;
- nextSibling = null;
- if (short) {
- const menuBtn = document.getElementById('menu-button');
- if (!menuBtn) return;
- yddAdded = true;
- menuBtn.addEventListener('click', createButton);
- return;
- }
- const topRow = document.getElementById('top-row');
- const menuBtn = topRow.querySelector('#button-shape');
- createSubscribeButton();
- if (!topRow || !menuBtn) return;
- yddAdded = true;
- menuBtn.addEventListener('click', createButton);
- }
- function onInit() {
- addStyles();
- const observer = new MutationObserver(function () {
- if (!yddAdded) return modifyMenu();
- if (oldHref != document.location.href) {
- oldHref = document.location.href;
- yddAdded = false;
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- });
- }