- // ==UserScript==
- // @name YouTube Direct Downloader (Cobalt)
- // @description Add a custom download button and provide options to download the video, video dubs, or audio directly from the YouTube page.
- // @icon https://www.google.com/s2/favicons?sz=64&domain=cobalt.tools
- // @version 1.4
- // @author afkarxyz
- // @namespace https://github.com/afkarxyz/userscripts/
- // @supportURL https://github.com/afkarxyz/userscripts/issues
- // @license MIT
- // @match https://www.youtube.com/*
- // @match https://youtube.com/*
- // @grant GM.xmlHttpRequest
- // @connect c.blahaj.ca
- // @connect dwnld.nichind.dev
- // @run-at document-end
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- const PRIMARY_API_URL = 'https://c.blahaj.ca/';
- const FALLBACK_API_URL = 'https://dwnld.nichind.dev/';
- let currentApiUrl = PRIMARY_API_URL;
-
- const LANGUAGE_MAP = {
- "af": "Afrikaans",
- "am": "አማርኛ",
- "ar": "العربية",
- "as": "Assamese",
- "az": "azərbaycan",
- "be": "Belarusian",
- "bg": "български",
- "bn": "বাংলা",
- "bs": "bosanski",
- "ca": "català",
- "cs": "čeština",
- "da": "dansk",
- "de": "Deutsch",
- "el": "Ελληνικά",
- "en": "English",
- "es": "español",
- "et": "eesti",
- "eu": "Basque",
- "fa": "فارسی",
- "fi": "suomi",
- "fil": "Filipino",
- "fr": "français",
- "gl": "Galician",
- "gu": "ગુજરાતી",
- "hi": "हिन्दी",
- "hr": "hrvatski",
- "hu": "magyar",
- "hy": "Armenian",
- "id": "Indonesia",
- "is": "Icelandic",
- "it": "italiano",
- "iw": "עברית",
- "ja": "日本語",
- "ka": "Georgian",
- "kk": "Kazakh",
- "km": "Khmer",
- "kn": "ಕನ್ನಡ",
- "ko": "한국어",
- "ky": "Kyrgyz",
- "lo": "Lao",
- "lt": "lietuvių",
- "lv": "latviešu",
- "mk": "Macedonian",
- "ml": "മലയാളം",
- "mn": "Mongolian",
- "mr": "मराठी",
- "ms": "Melayu",
- "my": "Burmese",
- "ne": "Nepali",
- "nl": "Nederlands",
- "no": "norsk",
- "or": "Odia",
- "pa": "ਪੰਜਾਬੀ",
- "pl": "polski",
- "pt": "português",
- "ro": "română",
- "ru": "русский",
- "si": "Sinhala",
- "sk": "slovenčina",
- "sl": "slovenščina",
- "sq": "Albanian",
- "sr": "српски",
- "sv": "svenska",
- "sw": "Kiswahili",
- "ta": "தமிழ்",
- "te": "తెలుగు",
- "th": "ไทย",
- "tr": "Türkçe",
- "uk": "українська",
- "ur": "اردو",
- "uz": "o'zbek",
- "vi": "Tiếng Việt",
- "zh-CN": "中文(中国)",
- "zh-HK": "中文(香港)",
- "zh-TW": "中文(台灣)",
- "zu": "Zulu"
- };
-
- const style = document.createElement('style');
- style.textContent = `
- .cobalt-download-btn {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- margin-left: 8px;
- transition: background-color 0.2s;
- }
- html[dark] .cobalt-download-btn {
- background-color: #ffffff1a;
- }
- html:not([dark]) .cobalt-download-btn {
- background-color: #0000000d;
- }
- html[dark] .cobalt-download-btn:hover {
- background-color: #ffffff33;
- }
- html:not([dark]) .cobalt-download-btn:hover {
- background-color: #00000014;
- }
- .cobalt-download-btn svg {
- width: 18px;
- height: 18px;
- }
- html[dark] .cobalt-download-btn svg {
- fill: var(--yt-spec-text-primary, #fff);
- }
- html:not([dark]) .cobalt-download-btn svg {
- fill: var(--yt-spec-text-primary, #030303);
- }
- `;
- document.head.appendChild(style);
-
- function triggerDirectDownload(url) {
- const a = document.createElement('a');
- a.style.display = 'none';
- a.href = url;
- document.body.appendChild(a);
- a.click();
- setTimeout(() => {
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }, 100);
- }
-
- function createDownloadDialog() {
- const dialog = document.createElement('div');
- dialog.className = 'yt-download-dialog';
- dialog.style.cssText = `
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: #000000;
- color: #e1e1e1;
- border-radius: 12px;
- box-shadow: 0 0 0 1px rgba(225,225,225,.1), 0 2px 4px 1px rgba(225,225,225,.18);
- font-family: 'IBM Plex Mono', 'Noto Sans Mono Variable', 'Noto Sans Mono', monospace;
- width: 400px;
- z-index: 9999;
- `;
-
- const dialogContent = document.createElement('div');
- dialogContent.style.padding = '16px';
-
- const styleElement = document.createElement('style');
- styleElement.textContent = `
- @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&display=swap');
-
- .quality-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 8px;
- margin-bottom: 16px;
- }
-
- .quality-option {
- display: flex;
- align-items: center;
- padding: 8px;
- cursor: pointer;
- }
-
- .quality-option:hover {
- background: #191919;
- border-radius: 6px;
- }
-
- .logo-container {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 16px;
- }
-
- .subtitle {
- color: #e1e1e1;
- opacity: 0.7;
- font-size: 12px;
- margin-top: 4px;
- }
-
- .title {
- font-size: 18px;
- font-weight: 700;
- }
-
- .title-link {
- text-decoration: none;
- color: inherit;
- cursor: pointer;
- transition: opacity 0.2s ease;
- }
-
- .title-link:hover {
- opacity: 0.8;
- }
-
- .codec-selector {
- margin-bottom: 16px;
- display: flex;
- gap: 8px;
- justify-content: center;
- }
-
- .codec-button {
- background: transparent;
- border: 1px solid #e1e1e1;
- color: #e1e1e1;
- padding: 6px 12px;
- border-radius: 14px;
- cursor: pointer;
- font-family: inherit;
- font-size: 12px;
- transition: all 0.2s ease;
- }
-
- .codec-button:hover {
- background: #808080;
- color: #000000;
- }
-
- .codec-button.selected {
- background: #1ed760;
- border-color: #1ed760;
- color: #000000;
- }
-
- .download-status {
- text-align: center;
- margin: 16px 0;
- font-size: 12px;
- display: none;
- }
-
- .button-container {
- display: flex;
- justify-content: center;
- gap: 8px;
- }
-
- .switch-container {
- position: absolute;
- top: 16px;
- right: 16px;
- display: flex;
- align-items: center;
- }
- .switch-button {
- background: transparent;
- border: none;
- cursor: pointer;
- padding: 4px;
- transition: all 0.2s ease;
- }
- .switch-button svg {
- width: 20px;
- height: 20px;
- fill: #e1e1e1;
- transition: all 0.2s ease;
- }
- .switch-button:hover svg {
- fill: #1ed760;
- }
- .audio-options {
- display: none;
- margin-bottom: 16px;
- }
- .audio-options.active {
- display: block;
- }
- .dub-selector {
- margin-top: 16px;
- margin-bottom: 16px;
- display: none;
- }
- .dub-select {
- width: 80%;
- margin: 0 auto;
- display: block;
- }
- .dub-button {
- background: transparent;
- border: 1px solid #39a9db;
- color: #39a9db;
- }
- .dub-button:hover {
- background: #39a9db;
- color: #000000;
- }
- .dub-button.selected {
- background: #39a9db;
- border-color: #39a9db;
- color: #000000;
- }
- `;
- dialog.appendChild(styleElement);
-
- const logoContainer = document.createElement('div');
- logoContainer.className = 'logo-container';
-
- const logoSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- logoSvg.setAttribute('width', '24');
- logoSvg.setAttribute('height', '16');
- logoSvg.setAttribute('viewBox', '0 0 24 16');
- logoSvg.setAttribute('fill', 'none');
-
- const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- path1.setAttribute('d', 'M0 15.6363L0 12.8594L9.47552 8.293L0 3.14038L0 0.363525L12.8575 7.4908V9.21862L0 15.6363Z');
- path1.setAttribute('fill', 'white');
-
- const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- path2.setAttribute('d', 'M11.1425 15.6363V12.8594L20.6181 8.293L11.1425 3.14038V0.363525L24 7.4908V9.21862L11.1425 15.6363Z');
- path2.setAttribute('fill', 'white');
-
- logoSvg.appendChild(path1);
- logoSvg.appendChild(path2);
-
- const logoDiv = document.createElement('div');
- logoDiv.id = 'cobalt-logo';
- logoDiv.appendChild(logoSvg);
-
- logoContainer.appendChild(logoDiv);
-
- const titleContainer = document.createElement('div');
- const titleLink = document.createElement('a');
- titleLink.href = 'https://gf.qytechs.cn/en/users/1376410';
- titleLink.target = '_blank';
- titleLink.rel = 'noopener noreferrer';
- titleLink.className = 'title-link';
-
- const title = document.createElement('div');
- title.className = 'title';
- title.textContent = 'cobalt';
-
- const statusSpan = document.createElement('span');
- statusSpan.id = 'api-status';
- statusSpan.style.marginLeft = '8px';
- statusSpan.style.fontSize = '14px';
- statusSpan.style.fontWeight = 'normal';
- statusSpan.textContent = 'checking...';
- title.appendChild(statusSpan);
-
- titleLink.appendChild(title);
-
- checkApiStatus(function(isOnline, apiType) {
- const statusSpan = document.getElementById('api-status');
- if (statusSpan) {
- statusSpan.textContent = isOnline ? apiType : 'Offline';
- statusSpan.style.color = isOnline ? '#1ed760' : '#f3727f';
- }
- });
-
- titleContainer.appendChild(titleLink);
-
- const subtitle = document.createElement('div');
- subtitle.className = 'subtitle';
- subtitle.textContent = 'YouTube Direct Downloader';
-
- titleContainer.appendChild(subtitle);
- logoContainer.appendChild(titleContainer);
-
- dialogContent.appendChild(logoContainer);
-
- const switchContainer = document.createElement('div');
- switchContainer.className = 'switch-container';
-
- const switchButton = document.createElement('button');
- switchButton.className = 'switch-button';
- switchButton.id = 'mode-switch';
-
- const switchSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- switchSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
- switchSvg.setAttribute('viewBox', '0 0 384 512');
-
- const switchPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- switchPath.setAttribute('d', 'M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zM64 288c0-17.7 14.3-32 32-32l96 0c17.7 0 32 14.3 32 32l0 96c0 17.7-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32l0-96zM300.9 397.9L256 368l0-64 44.9-29.9c2-1.3 4.4-2.1 6.8-2.1c6.8 0 12.3 5.5 12.3 12.3l0 103.4c0 6.8-5.5 12.3-12.3 12.3c-2.4 0-4.8-.7-6.8-2.1z');
-
- switchSvg.appendChild(switchPath);
- switchButton.appendChild(switchSvg);
- switchContainer.appendChild(switchButton);
-
- dialogContent.appendChild(switchContainer);
-
- const videoOptions = document.createElement('div');
- videoOptions.id = 'video-options';
-
- const videoCodecSelector = document.createElement('div');
- videoCodecSelector.className = 'codec-selector';
-
- ['h264', 'vp9', 'av1'].forEach(codec => {
- const button = document.createElement('button');
- button.className = 'codec-button';
- button.dataset.codec = codec;
- button.textContent = codec.toUpperCase();
- videoCodecSelector.appendChild(button);
- });
-
- const dubButton = document.createElement('button');
- dubButton.className = 'codec-button dub-button';
- dubButton.dataset.codec = 'dub';
- dubButton.textContent = 'DUB';
- videoCodecSelector.appendChild(dubButton);
-
- videoOptions.appendChild(videoCodecSelector);
-
- const qualityOptions = document.createElement('div');
- qualityOptions.id = 'quality-options';
- qualityOptions.className = 'quality-grid';
- videoOptions.appendChild(qualityOptions);
-
- const dubSelector = document.createElement('div');
- dubSelector.className = 'dub-selector';
- dubSelector.style.display = 'none';
-
- const dubSelect = document.createElement('select');
- dubSelect.className = 'dub-select';
- dubSelect.style.cssText = `
- padding: 8px;
- background: #191919;
- color: #e1e1e1;
- border: 1px solid #e1e1e1;
- border-radius: 6px;
- font-family: inherit;
- cursor: pointer;
- `;
-
- const defaultOption = document.createElement('option');
- defaultOption.value = '';
- defaultOption.textContent = 'Original Audio';
- dubSelect.appendChild(defaultOption);
-
- Object.entries(LANGUAGE_MAP).forEach(([code, name]) => {
- const option = document.createElement('option');
- option.value = code;
- option.textContent = `${name} (${code})`;
- dubSelect.appendChild(option);
- });
-
- dubSelector.appendChild(dubSelect);
- videoOptions.appendChild(dubSelector);
-
- dialogContent.appendChild(videoOptions);
-
- const audioOptions = document.createElement('div');
- audioOptions.id = 'audio-options';
- audioOptions.className = 'audio-options';
-
- const audioCodecSelector = document.createElement('div');
- audioCodecSelector.className = 'codec-selector';
-
- ['mp3', 'ogg', 'opus', 'wav'].forEach(codec => {
- const button = document.createElement('button');
- button.className = 'codec-button';
- button.dataset.codec = codec;
- button.textContent = codec.toUpperCase();
- audioCodecSelector.appendChild(button);
- });
-
- audioOptions.appendChild(audioCodecSelector);
-
- const bitrateOptions = document.createElement('div');
- bitrateOptions.id = 'bitrate-options';
- bitrateOptions.className = 'quality-grid';
- audioOptions.appendChild(bitrateOptions);
-
- dialogContent.appendChild(audioOptions);
-
- const downloadStatus = document.createElement('div');
- downloadStatus.className = 'download-status';
- downloadStatus.id = 'download-status';
- dialogContent.appendChild(downloadStatus);
-
- const buttonContainer = document.createElement('div');
- buttonContainer.className = 'button-container';
-
- const cancelButton = document.createElement('button');
- cancelButton.id = 'cancel-button';
- cancelButton.textContent = 'Cancel';
- cancelButton.style.cssText = `
- background: transparent;
- border: 1px solid #e1e1e1;
- color: #e1e1e1;
- font-size: 14px;
- font-weight: 500;
- padding: 8px 16px;
- cursor: pointer;
- font-family: inherit;
- border-radius: 18px;
- `;
-
- const downloadButton = document.createElement('button');
- downloadButton.id = 'download-button';
- downloadButton.textContent = 'Download';
- downloadButton.style.cssText = `
- background: transparent;
- border: 1px solid #e1e1e1;
- color: #e1e1e1;
- font-size: 14px;
- font-weight: 500;
- padding: 8px 16px;
- border-radius: 18px;
- cursor: pointer;
- font-family: inherit;
- `;
-
- buttonContainer.appendChild(cancelButton);
- buttonContainer.appendChild(downloadButton);
-
- dialogContent.appendChild(buttonContainer);
-
- dialog.appendChild(dialogContent);
-
- const backdrop = document.createElement('div');
- backdrop.style.cssText = `
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- z-index: 9998;
- `;
- document.body.appendChild(backdrop);
-
- backdrop.addEventListener('click', () => {
- closeDialog(dialog, backdrop);
- });
-
- const savedCodec = localStorage.getItem('cobaltToolsCodec') || 'h264';
- const savedQuality = localStorage.getItem('cobaltToolsQuality') || '1080p';
- const savedMode = localStorage.getItem('cobaltToolsMode') || 'video';
- const savedAudioCodec = localStorage.getItem('cobaltToolsAudioCodec') || 'mp3';
- const savedDub = localStorage.getItem('cobaltToolsDub') || '';
-
- return { dialog, backdrop, savedCodec, savedQuality, savedMode, savedAudioCodec, savedDub };
- }
-
- function closeDialog(dialog, backdrop) {
- dialog.remove();
- backdrop.remove();
- }
-
- function extractVideoId(url) {
- const urlObj = new URL(url);
- const searchParams = new URLSearchParams(urlObj.search);
- return searchParams.get('v');
- }
-
- function checkApiStatus(callback) {
- checkSingleApiStatus(PRIMARY_API_URL, function(isOnline) {
- if (isOnline) {
- currentApiUrl = PRIMARY_API_URL;
- callback(true, 'Primary');
- } else {
- checkSingleApiStatus(FALLBACK_API_URL, function(fallbackIsOnline) {
- if (fallbackIsOnline) {
- currentApiUrl = FALLBACK_API_URL;
- callback(true, 'Fallback');
- } else {
- currentApiUrl = PRIMARY_API_URL;
- callback(false, 'Offline');
- }
- });
- }
- });
- }
-
- function checkSingleApiStatus(apiUrl, callback) {
- GM.xmlHttpRequest({
- method: 'GET',
- url: apiUrl,
- timeout: 5000,
- onload: function(response) {
- callback(response.status >= 200 && response.status < 300);
- },
- onerror: function() {
- callback(false);
- },
- ontimeout: function() {
- callback(false);
- }
- });
- }
-
- function downloadVideo(quality, videoId, codec, dialog, backdrop) {
- const statusElement = dialog.querySelector('#download-status');
- statusElement.style.display = 'block';
- statusElement.textContent = 'Preparing download...';
-
- const dubSelect = dialog.querySelector('.dub-select');
- const selectedDub = dubSelect ? dubSelect.value : '';
-
- const payload = {
- url: `https://www.youtube.com/watch?v=${videoId}`,
- downloadMode: "auto",
- filenameStyle: "basic",
- videoQuality: quality.replace('p', ''),
- youtubeVideoCodec: codec,
- youtubeDubLang: selectedDub ? selectedDub : 'original'
- };
-
- function attemptDownload(apiUrl, isRetry = false) {
- statusElement.textContent = isRetry ? 'Trying fallback API...' : 'Preparing download...';
-
- GM.xmlHttpRequest({
- method: 'POST',
- url: apiUrl,
- headers: {
- 'accept': 'application/json',
- 'content-type': 'application/json'
- },
- data: JSON.stringify(payload),
- responseType: 'json',
- onload: function(response) {
- try {
- const data = JSON.parse(response.responseText);
- if (data.url) {
- statusElement.textContent = 'Starting download...';
- triggerDirectDownload(data.url);
-
- setTimeout(() => {
- closeDialog(dialog, backdrop);
- }, 1000);
- } else {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Error: No download URL found';
- console.error('No URL in response:', data);
- }
- }
- } catch (error) {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Error: API service might be temporarily unavailable';
- console.error('Error processing response:', error);
- }
- }
- },
- onerror: function(error) {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Network error. Please check your connection.';
- console.error('Network error:', error);
- }
- }
- });
- }
-
- attemptDownload(currentApiUrl);
- }
-
- function updateQualityOptions(dialog, codec, savedQuality) {
- const qualityOptions = dialog.querySelector('#quality-options');
- while (qualityOptions.firstChild) {
- qualityOptions.removeChild(qualityOptions.firstChild);
- }
-
- let qualities;
- if (codec === 'h264') {
- qualities = ['144p', '240p', '360p', '480p', '720p', '1080p'];
- } else if (codec === 'vp9') {
- qualities = ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '4k'];
- } else {
- qualities = ['144p', '240p', '360p', '480p', '720p', '1080p', '1440p', '4k', '8k+'];
- }
-
- qualities.forEach((quality, index) => {
- const option = document.createElement('div');
- option.className = 'quality-option';
-
- const input = document.createElement('input');
- input.type = 'radio';
- input.id = `quality-${index}`;
- input.name = 'quality';
- input.value = quality;
- input.style.marginRight = '8px';
-
- const label = document.createElement('label');
- label.htmlFor = `quality-${index}`;
- label.style.fontSize = '14px';
- label.style.cursor = 'pointer';
- label.textContent = quality;
-
- option.appendChild(input);
- option.appendChild(label);
- qualityOptions.appendChild(option);
-
- option.addEventListener('click', function() {
- const radioButton = this.querySelector('input[type="radio"]');
- qualityOptions.querySelectorAll('input[type="radio"]').forEach(rb => {
- rb.checked = false;
- });
- radioButton.checked = true;
-
- localStorage.setItem('cobaltToolsQuality', quality);
- });
- });
-
- const defaultQuality = qualities.includes(savedQuality) ? savedQuality : qualities[qualities.length - 1];
- const defaultRadio = dialog.querySelector(`input[name="quality"][value="${defaultQuality}"]`);
- if (defaultRadio) {
- defaultRadio.checked = true;
- }
- }
-
- function updateAudioOptions(dialog, codec, savedBitrate) {
- const bitrateOptions = dialog.querySelector('#bitrate-options');
- while (bitrateOptions.firstChild) {
- bitrateOptions.removeChild(bitrateOptions.firstChild);
- }
-
- if (codec === 'wav') {
- return;
- }
-
- const bitrates = ['8', '64', '96', '128', '256', '320'];
-
- bitrates.forEach((bitrate, index) => {
- const option = document.createElement('div');
- option.className = 'quality-option';
-
- const input = document.createElement('input');
- input.type = 'radio';
- input.id = `bitrate-${index}`;
- input.name = 'bitrate';
- input.value = bitrate;
- input.style.marginRight = '8px';
-
- const label = document.createElement('label');
- label.htmlFor = `bitrate-${index}`;
- label.style.fontSize = '14px';
- label.style.cursor = 'pointer';
- label.textContent = `${bitrate} kb/s`;
-
- option.appendChild(input);
- option.appendChild(label);
- bitrateOptions.appendChild(option);
-
- option.addEventListener('click', function() {
- const radioButton = this.querySelector('input[type="radio"]');
- bitrateOptions.querySelectorAll('input[type="radio"]').forEach(rb => {
- rb.checked = false;
- });
- radioButton.checked = true;
-
- localStorage.setItem('cobaltToolsBitrate', bitrate);
- });
- });
-
- const defaultBitrate = bitrates.includes(savedBitrate) ? savedBitrate : bitrates[bitrates.length - 1];
- const defaultRadio = dialog.querySelector(`input[name="bitrate"][value="${defaultBitrate}"]`);
- if (defaultRadio) {
- defaultRadio.checked = true;
- }
- }
-
- function downloadAudio(format, bitrate, videoId, dialog, backdrop) {
- const statusElement = dialog.querySelector('#download-status');
- statusElement.style.display = 'block';
- statusElement.textContent = 'Preparing audio download...';
-
- let payload;
- if (format === 'wav') {
- payload = {
- url: `https://www.youtube.com/watch?v=${videoId}`,
- downloadMode: "audio",
- filenameStyle: "basic",
- audioFormat: "wav"
- };
- } else {
- payload = {
- url: `https://www.youtube.com/watch?v=${videoId}`,
- downloadMode: "audio",
- filenameStyle: "basic",
- audioFormat: format,
- audioBitrate: bitrate
- };
- }
-
- function attemptDownload(apiUrl, isRetry = false) {
- statusElement.textContent = isRetry ? 'Trying fallback API...' : 'Preparing audio download...';
-
- GM.xmlHttpRequest({
- method: 'POST',
- url: apiUrl,
- headers: {
- 'accept': 'application/json',
- 'content-type': 'application/json'
- },
- data: JSON.stringify(payload),
- responseType: 'json',
- onload: function(response) {
- try {
- const data = JSON.parse(response.responseText);
- if (data.url) {
- statusElement.textContent = 'Starting audio download...';
- triggerDirectDownload(data.url);
-
- setTimeout(() => {
- closeDialog(dialog, backdrop);
- }, 1000);
- } else {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Error: No download URL found';
- console.error('No URL in response:', data);
- }
- }
- } catch (error) {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Error: API service might be temporarily unavailable';
- console.error('Error processing response:', error);
- }
- }
- },
- onerror: function(error) {
- if (!isRetry && apiUrl === PRIMARY_API_URL) {
- attemptDownload(FALLBACK_API_URL, true);
- } else {
- statusElement.textContent = 'Network error. Please check your connection.';
- console.error('Network error:', error);
- }
- }
- });
- }
-
- attemptDownload(currentApiUrl);
- }
-
- function updateModeSwitch(modeSwitch, isAudioMode) {
- while (modeSwitch.firstChild) {
- modeSwitch.removeChild(modeSwitch.firstChild);
- }
-
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
- svg.setAttribute('viewBox', '0 0 384 512');
-
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-
- if (isAudioMode) {
- path.setAttribute('d', 'M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zm2 226.3c37.1 22.4 62 63.1 62 109.7s-24.9 87.3-62 109.7c-7.6 4.6-17.4 2.1-22-5.4s-2.1-17.4 5.4-22C269.4 401.5 288 370.9 288 336s-18.6-65.5-46.5-82.3c-7.6-4.6-10-14.4-5.4-22s14.4-10 22-5.4zm-91.9 30.9c6 2.5 9.9 8.3 9.9 14.8l0 128c0 6.5-3.9 12.3-9.9 14.8s-12.9 1.1-17.4-3.5L113.4 376 80 376c-8.8 0-16-7.2-16-16l0-48c0-8.8 7.2-16 16-16l33.4 0 35.3-35.3c4.6-4.6 11.5-5.9 17.4-3.5zm51 34.9c6.6-5.9 16.7-5.3 22.6 1.3C249.8 304.6 256 319.6 256 336s-6.2 31.4-16.3 42.7c-5.9 6.6-16 7.1-22.6 1.3s-7.1-16-1.3-22.6c5.1-5.7 8.1-13.1 8.1-21.3s-3.1-15.7-8.1-21.3c-5.9-6.6-5.3-16.7 1.3-22.6z');
- } else {
- path.setAttribute('d', 'M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zM64 288c0-17.7 14.3-32 32-32l96 0c17.7 0 32 14.3 32 32l0 96c0 17.7-14.3 32-32 32l-96 0c-17.7 0-32-14.3-32-32l0-96zM300.9 397.9L256 368l0-64 44.9-29.9c2-1.3 4.4-2.1 6.8-2.1c6.8 0 12.3 5.5 12.3 12.3l0 103.4c0 6.8-5.5 12.3-12.3 12.3c-2.4 0-4.8-.7-6.8-2.1z');
- }
-
- svg.appendChild(path);
- modeSwitch.appendChild(svg);
- }
-
- function createDownloadButton() {
- const downloadButton = document.createElement('div');
- downloadButton.className = 'cobalt-download-btn';
-
- const svgNS = "http://www.w3.org/2000/svg";
- const svg = document.createElementNS(svgNS, "svg");
- svg.setAttribute("viewBox", "0 0 512 512");
-
- const path = document.createElementNS(svgNS, "path");
- path.setAttribute("d", "M256 464c114.9 0 208-93.1 208-208c0-13.3 10.7-24 24-24s24 10.7 24 24c0 141.4-114.6 256-256 256S0 397.4 0 256c0-13.3 10.7-24 24-24s24 10.7 24 24c0 114.9 93.1 208 208 208zM377.6 232.3l-104 112c-4.5 4.9-10.9 7.7-17.6 7.7s-13-2.8-17.6-7.7l-104-112c-9-9.7-8.5-24.9 1.3-33.9s24.9-8.5 33.9 1.3L232 266.9 232 24c0-13.3 10.7-24 24-24s24 10.7 24 24l0 242.9 62.4-67.2c9-9.7 24.2-10.3 33.9-1.3s10.3 24.2 1.3 33.9z");
-
- svg.appendChild(path);
- downloadButton.appendChild(svg);
-
- downloadButton.addEventListener('click', function() {
- const customDialog = modifyQualityOptionsAndRemoveElements();
- document.body.appendChild(customDialog);
- });
-
- return downloadButton;
- }
-
- function insertDownloadButton() {
- const targetSelector = '#owner';
- const target = document.querySelector(targetSelector);
-
- if (target && !document.querySelector('.cobalt-download-btn')) {
- const downloadButton = createDownloadButton();
- target.appendChild(downloadButton);
- }
- }
-
- function modifyQualityOptionsAndRemoveElements() {
- const { dialog, backdrop, savedCodec, savedMode, savedAudioCodec, savedDub } = createDownloadDialog();
- let currentVideoId = null;
- let selectedVideoCodec = savedCodec;
- let selectedAudioCodec = savedAudioCodec;
- let isAudioMode = savedMode === 'audio';
-
- try {
- const url = window.location.href;
- currentVideoId = extractVideoId(url);
- } catch (error) {
- console.error('Error extracting video ID:', error);
- return;
- }
-
- const modeSwitch = dialog.querySelector('#mode-switch');
- const videoOptions = dialog.querySelector('#video-options');
- const audioOptions = dialog.querySelector('#audio-options');
- const dubSelector = dialog.querySelector('.dub-selector');
-
- function updateModeSwitchAndOptions() {
- updateModeSwitch(modeSwitch, isAudioMode);
- if (isAudioMode) {
- audioOptions.style.display = 'block';
- videoOptions.style.display = 'none';
- } else {
- videoOptions.style.display = 'block';
- audioOptions.style.display = 'none';
- }
- }
-
- updateModeSwitchAndOptions();
-
- modeSwitch.addEventListener('click', () => {
- isAudioMode = !isAudioMode;
- updateModeSwitchAndOptions();
- localStorage.setItem('cobaltToolsMode', isAudioMode ? 'audio' : 'video');
- updateCodecButtons();
- });
-
- function updateCodecButtons() {
- const videoCodecButtons = videoOptions.querySelectorAll('.codec-button');
- const audioCodecButtons = audioOptions.querySelectorAll('.codec-button');
-
- videoCodecButtons.forEach(button => {
- button.classList.remove('selected');
- if (button.dataset.codec === selectedVideoCodec) {
- button.classList.add('selected');
- }
- });
-
- audioCodecButtons.forEach(button => {
- button.classList.remove('selected');
- if (button.dataset.codec === selectedAudioCodec) {
- button.classList.add('selected');
- }
- });
-
- if (isAudioMode) {
- updateAudioOptions(dialog, selectedAudioCodec, localStorage.getItem('cobaltToolsBitrate') || '320');
- } else {
- updateQualityOptions(dialog, selectedVideoCodec, localStorage.getItem('cobaltToolsQuality') || '1080p');
- }
-
- if (selectedVideoCodec === 'dub') {
- dubSelector.style.display = 'block';
- dialog.querySelector('#quality-options').style.display = 'none';
- } else {
- dubSelector.style.display = 'none';
- dialog.querySelector('#quality-options').style.display = 'grid';
- }
- }
-
- const codecButtons = dialog.querySelectorAll('.codec-button');
- codecButtons.forEach(button => {
- button.addEventListener('click', () => {
- if (isAudioMode) {
- selectedAudioCodec = button.dataset.codec;
- localStorage.setItem('cobaltToolsAudioCodec', selectedAudioCodec);
- } else {
- selectedVideoCodec = button.dataset.codec;
- localStorage.setItem('cobaltToolsCodec', selectedVideoCodec);
- }
- updateCodecButtons();
- });
- });
-
- updateCodecButtons();
-
- const dubSelect = dialog.querySelector('.dub-select');
- if (dubSelect) {
- dubSelect.value = savedDub;
- dubSelect.addEventListener('change', () => {
- localStorage.setItem('cobaltToolsDub', dubSelect.value);
- });
- }
-
- const cancelButton = dialog.querySelector('#cancel-button');
- const downloadButton = dialog.querySelector('#download-button');
-
- if (cancelButton) {
- cancelButton.addEventListener('click', () => closeDialog(dialog, backdrop));
- cancelButton.addEventListener('mouseover', () => {
- cancelButton.style.background = '#f3727f';
- cancelButton.style.borderColor = '#f3727f';
- cancelButton.style.color = '#000000';
- });
- cancelButton.addEventListener('mouseout', () => {
- cancelButton.style.background = 'transparent';
- cancelButton.style.borderColor = '#e1e1e1';
- cancelButton.style.color = '#e1e1e1';
- });
- }
-
- if (downloadButton) {
- downloadButton.addEventListener('click', () => {
- if (isAudioMode) {
- const selectedFormat = selectedAudioCodec;
- const selectedBitrate = selectedFormat === 'wav' ? 'WAV' : dialog.querySelector('input[name="bitrate"]:checked')?.value || '320';
- if (selectedFormat && currentVideoId) {
- downloadAudio(selectedFormat, selectedBitrate, currentVideoId, dialog, backdrop);
- }
- } else {
- if (selectedVideoCodec === 'dub') {
- downloadVideo('dub', currentVideoId, 'dub', dialog, backdrop);
- } else {
- const selectedQuality = dialog.querySelector('input[name="quality"]:checked');
- if (selectedQuality && currentVideoId) {
- downloadVideo(selectedQuality.value, currentVideoId, selectedVideoCodec, dialog, backdrop);
- }
- }
- }
- });
- downloadButton.addEventListener('mouseover', () => {
- downloadButton.style.background = '#1ed760';
- downloadButton.style.borderColor = '#1ed760';
- downloadButton.style.color = '#000000';
- });
- downloadButton.addEventListener('mouseout', () => {
- downloadButton.style.background = 'transparent';
- downloadButton.style.borderColor = '#e1e1e1';
- downloadButton.style.color = '#e1e1e1';
- });
- }
-
- return dialog;
- }
-
- const observer = new MutationObserver(() => {
- if (window.location.pathname.includes('/watch')) {
- insertDownloadButton();
- }
- });
-
- observer.observe(document.body, { childList: true, subtree: true });
-
- insertDownloadButton();
-
- window.addEventListener('yt-navigate-finish', insertDownloadButton);
- })();