Greasy Fork is available in English.

YouTube Cobalt Tools Download Button

Adds a download button to YouTube videos using Cobalt API for downloading videos or audio.

< Feedback on YouTube Cobalt Tools Download Button

Review: Good - script works

Cobalt blocked api use now I have fixed it by going to the site itself

// ==UserScript==
// @name         YouTube Cobalt Tools Download Button
// @namespace   http://tampermonkey.net/
// @version     0.4
// @description Adds a download button to YouTube videos using Cobalt API for downloading videos or audio.
// @author       yodaluca23
// @license     GNU GPLv3
// @match       https://*.youtube.com/*
// @match       https://cobalt.tools/
// @grant       GM.xmlHttpRequest
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_openInTab
// ==/UserScript==

(function() {
    'use strict';
    let lastFetchedQualities = [];
    let currentPageUrl = window.location.href;
    let initialInjectDelay = 2000;
    let navigationInjectDelay = 1000;

    function isYouTubeWatchURL() {
        return window.location.href.includes("youtube.com/watch?");
    }
    function fetchVideoQualities(callback) {
        GM.xmlHttpRequest({
            method: 'GET',
            url: window.location.href,
            headers: {
                'User-Agent': navigator.userAgent,
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
            },
            onload: function(response) {
                if (response.status === 200) {
                    const videoQualities = extractQualities(response.responseText);
                    const strippedQualities = stripQualityLabels(videoQualities);
                    const filteredQualities = filterAndRemoveDuplicates(strippedQualities);
                    console.log('Video Qualities:', filteredQualities);

                    lastFetchedQualities = filteredQualities;

                    callback(filteredQualities);
                } else {
                    console.error('Failed to fetch video qualities. Status:', response.status);
                    callback([]);
                }
            },
            onerror: function(err) {
                console.error('Error fetching YouTube video page:', err);
                callback([]);
            }
        });
    }

    function extractQualities(html) {
        const regex = /"(qualityLabel|width)":"([^"]+)"/g;
        const qualities = [];
        let match;
        while ((match = regex.exec(html)) !== null) {
            if (match[1] === 'qualityLabel') {
                qualities.push(match[2]);
            }
        }
        return qualities;
    }

    function stripQualityLabels(qualities) {
        return qualities.map(quality => {
            const index = quality.indexOf('p');
            return index !== -1 ? quality.substring(0, index + 1) : quality;
        });
    }

    function filterAndRemoveDuplicates(qualities) {
        const filteredQualities = [];
        const seenQualities = new Set();
        for (let quality of qualities) {
            if (!quality.includes('Premium') && !seenQualities.has(quality)) {
                filteredQualities.push(quality);
                seenQualities.add(quality);
            }
        }

        filteredQualities.sort((a, b) => compareQuality(a, b));
        return filteredQualities;
    }

    function compareQuality(a, b) {
        const regex = /(\d+)p/;
        const resA = parseInt(a.match(regex)[1]);
        const resB = parseInt(b.match(regex)[1]);

        return resB - resA;
    }

    function arraysEqual(arr1, arr2) {
        if (arr1.length !== arr2.length) return false;
        for (let i = 0; i < arr1.length; i++) {
            if (arr1[i] !== arr2[i]) return false;
        }
        return true;
    }

    function injectDownloadButton() {
        setTimeout(() => {
            const existingButton = document.getElementById('cobalt-download-btn');
            if (existingButton) {
                existingButton.remove();
            }
            const downloadButton = document.createElement('button');
            downloadButton.id = 'cobalt-download-btn';
            downloadButton.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';
            downloadButton.setAttribute('aria-label', 'Download');
            downloadButton.setAttribute('title', 'Download');
            downloadButton.innerHTML = `
                 <div class="yt-spec-button-shape-next__icon">
                    <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false" style="pointer-events: none; display: inline-block; width: 24px; height: 24px; vertical-align: middle;">
                        <path fill="currentColor" d="M17 18v1H6v-1h11zm-.5-6.6-.7-.7-3.8 3.7V4h-1v10.4l-3.8-3.8-.7.7 5 5 5-4.9z"></path>
                    </svg>
                </div>
                <div class="yt-spec-button-shape-next__button-text-content">Download</div>
            `;
            downloadButton.style.backgroundColor = 'rgb(44, 44, 44)';
            downloadButton.style.border = '0px solid rgb(204, 204, 204)';
            downloadButton.style.borderRadius = '30px';
            downloadButton.style.fontSize = '14px';
            downloadButton.style.padding = '8px 16px';
            downloadButton.style.cursor = 'pointer';
            downloadButton.style.marginLeft = '8px';
            downloadButton.style.marginRight = '0px';
            downloadButton.onclick = () => showQualityPopup(currentPageUrl);
            const actionMenu = document.querySelector('.top-level-buttons');
            actionMenu.appendChild(downloadButton);
        }, initialInjectDelay);
    }

    function removeNativeDownloadButton() {
        setTimeout(() => {
            const nativeDownloadButtonInOverflow = document.querySelector('ytd-menu-service-item-download-renderer');
            if (nativeDownloadButtonInOverflow) {
                nativeDownloadButtonInOverflow.remove();
            }

            const nativeDownloadButton = document.querySelector('ytd-download-button-renderer');
            if (nativeDownloadButton) {
                nativeDownloadButton.remove();
            }
        }, initialInjectDelay);
    }

    function showQualityPopup(videoUrl) {
        fetchVideoQualities((qualities) => {
            const formatOptions = ['mp4', 'webm', 'ogg', 'mp3', 'opus', 'wav'];
            const qualityPrompt = `
                <div id="cobalt-quality-picker" style="
                    background: black;
                    padding: 20px;
                    border: 1px solid #ccc;
                    border-radius: 10px;
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    z-index: 9999;
                    max-width: 300px;
                    width: 100%;
                    max-height: 400px;
                    overflow-y: auto;
                    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
                    <h2 style="text-align: center; margin-bottom: 20px; font-size: 1.5em; color: #fff;">Select Quality</h2>
                    <label for="cobalt-format" style="display: block; margin-bottom: 10px; font-weight: bold; color: #fff; ">Format:</label>
                    <select id="cobalt-format" style="margin-bottom: 10px; width: 100%; padding: 5px; border-radius: 5px; border: 1px solid #ccc; color: #fff; background: black;">
                        ${formatOptions.map(format => `<option value="${format}">${format}</option>`).join('')}
                    </select>
                    <label id="quality-label" for="cobalt-quality" style="display: block; margin-bottom: 10px; font-weight: bold; color: #fff;">Quality:</label>
                    <select id="cobalt-quality" style="margin-bottom: 10px; width: 100%; padding: 5px; border-radius: 5px; border: 1px solid #ccc; color: #fff; background: black;">
                        ${qualities.map(q => `<option value="${q}">${q}</option>`).join('')}
                    </select>
                    <div id="cobalt-loading" style="display: none; margin-bottom: 10px; text-align: center; color: #fff;">Loading...</div>
                        <button id="cobalt-start-download" style="display: block; width: 100%; padding: 10px; background-color: black; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Download</button>
                    </div>`
            const popupContainer = document.createElement('div');
            popupContainer.innerHTML = qualityPrompt;
            document.body.appendChild(popupContainer);

            document.addEventListener('click', (event) => {
                if (!popupContainer.contains(event.target)) {
                    document.body.removeChild(popupContainer);
                }
            }, { once: true });

            const qualityDropdown = document.getElementById('cobalt-quality');
            const loadingIndicator = document.getElementById('cobalt-loading');
            const formatDropdown = document.getElementById('cobalt-format');
            const startDownloadBtn = document.getElementById('cobalt-start-download');

            formatDropdown.addEventListener('change', () => {
                const isAudioFormat = formatDropdown.value === 'mp3' || formatDropdown.value === 'opus' || formatDropdown.value === 'wav';
                const qualityLabel = document.getElementById('quality-label');
                if (isAudioFormat) {
                    qualityLabel.style.display = 'none';
                    qualityDropdown.style.display = 'none';
                } else {
                    qualityLabel.style.display = 'block';
                    qualityDropdown.style.display = 'block';
                }
            });

            startDownloadBtn.addEventListener('click', async () => {
                try {
                    loadingIndicator.style.display = 'block';
                    startDownloadBtn.disabled = true;
                    startDownloadBtn.style.cursor = 'not-allowed';
                    const format = formatDropdown.value;
                    const quality = qualityDropdown.value;
                    GM_setValue("quality", quality);
                    GM_setValue("format", format);
                    GM_setValue("url", window.location.href);
                    const url = 'https://cobalt.tools';
                    try {
                        GM_openInTab(url, true);
                    } catch (e) {
                        window.open(url, '_blank');
                    }
                } catch (err) {
                    console.error('Error fetching download URL:', err);
                    GM_notification('Failed to fetch download link. Please try again.', 'Error');
                } finally {
                    loadingIndicator.style.display = 'none';
                    startDownloadBtn.disabled = false;
                    startDownloadBtn.style.cursor = 'pointer';
                }

                document.body.removeChild(popupContainer);
            });
        });
    }

    function initializeDownloadButton() {
        injectDownloadButton();
        removeNativeDownloadButton();
    }

    if(window.location.href.startsWith('https://cobalt')) {
        const format = GM_getValue("format");
        document.querySelector("#settings-footer").click();
        const settings = document.getElementsByClassName("switch");

        let thing = '';

        thing = (format == "webm" || format == "mp4") ? GM_getValue("quality") : format;
        if(format == "webm" || format == "mp4") document.querySelector('#tab-button-settings-audio').click();

        //console.log(quality);
        for (let i = 0; i < settings.length; i++) {
         // console.log(settings[i].innerText);
         // This works but sometimes it just fail to download above 1080p
            if (settings[i].textContent == thing) {
                settings[i].click();
                break;
            }
        }
        if (format == "webm") document.querySelector("#vCodec-vp9").click();
        document.querySelector(".tab-settings.switch.back-button").click();
        if((format != "webm" && format != "mp4")) document.querySelector('#audioMode-true').click();

        download(GM_getValue("url")); //This the method on cobalt website
    }
    else {
        if (isYouTubeWatchURL()) {
        setTimeout(() => {
            initializeDownloadButton();
        }, initialInjectDelay);
        }

        window.onpopstate = function(event) {
            setTimeout(() => {
                if (currentPageUrl !== window.location.href) {
                    currentPageUrl = window.location.href;
                    console.log('URL changed:', currentPageUrl);
                    if (isYouTubeWatchURL()) {
                    initializeDownloadButton();
                    }
                    const existingPopup = document.querySelector('#cobalt-quality-picker');
                    if (existingPopup) {
                        existingPopup.remove();
                    }
                }
            }, navigationInjectDelay);
        };

        const observer = new MutationObserver(mutations => {
            for (let mutation of mutations) {
                if (mutation.type === 'childList' && mutation.target.classList.contains('html5-video-player')) {
                    console.log('Video player changed');
                    setTimeout(() => {
                        currentPageUrl = window.location.href;
                        if (isYouTubeWatchURL()) {
                        initializeDownloadButton();
                        }
                    }, navigationInjectDelay);

                    const existingPopup = document.querySelector('#cobalt-quality-picker');
                    if (existingPopup) {
                        existingPopup.remove();
                    }
                    break;
                }
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true,
        });
    }
})();
Luca LeBlancAuthor
§
Posted: 02.07.2024

I talked to them about it in their Discord, it should be fixed now, it was mistakenly taken down with the goodtube take down. https://github.com/imputnet/cobalt/issues/600

§
Posted: 02.07.2024
Edited: 02.07.2024

Bruh

Please still change // @match *://*.youtube.com/*
To
// @match https://www.youtube.com/*
Because Cromite recognizes scripts as invalid if there is a * in the domain

Also I styled the quality popup a bit take a look at it if you want

Post reply

Sign in to post a reply.