Greasy Fork is available in English.

Vimeo Video Downloader

Provides buttons to extract the video

// ==UserScript==
// @name         Vimeo Video Downloader
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Provides buttons to extract the video
// @author       Tristan Reeves
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let baseUrl = '';
    let finalUrl = '';

    // Function to handle the URL and response
    async function handleUrl(url, responseText) {
        if (url.includes("playlist.json")) {
            // Extract the base URL up to "/v2"
            baseUrl = url.split('/v2')[0];

            // Parse the JSON response to extract the audio ID
            try {
                const data = JSON.parse(responseText);

                // Extract and log video resolutions
                if (data.video) {
                    for (let key in data.video) {
                        if (data.video[key] && data.video[key].height) {
                            let height = data.video[key].height;
                            let id = data.video[key].id || 'No ID';

                            let resolution = height === 240 ? '240px' : `${height}px`;
                            const RESurl = `${baseUrl}/parcel/video/${id}.mp4`;
                            console.log(`Resolution: ${resolution}, url: ${RESurl}`);

                            // Save resolution data for use
                            if (!window.resolutions) {
                                window.resolutions = {};
                            }
                            window.resolutions[id] = resolution;
                        }
                    }
                }

                if (data.audio && data.audio.length > 0) {
                    for (let audioItem of data.audio) {
                        if (audioItem.id) {
                            let audioId = audioItem.id;

                            // Construct the final URL
                            finalUrl = `${baseUrl}/parcel/video/${audioId}.mp4`;
                            console.log("Audio+ URL: ", finalUrl);

                            // Create or update the MP4 button
                            createMp4Button(finalUrl);

                            break; // Stop after the first valid audio ID
                        }
                    }
                }
            } catch (e) {
                console.error("Error parsing JSON response:", e);
            }
        }
    }

    function createMp4Button(finalUrl) {
        // Remove any existing buttons to avoid duplicates
        removeExistingButtons();

        // Create the MP4 button
        const mp4Button = document.createElement('button');
        mp4Button.id = 'vimeo-mp4-button';
        mp4Button.textContent = 'Mp4';
        mp4Button.style.position = 'fixed';
        mp4Button.style.top = '20px';
        mp4Button.style.right = '20px';
        mp4Button.style.padding = '8px 12px';
        mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.2)'; // Transparent blue
        mp4Button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
        mp4Button.style.border = 'none';
        mp4Button.style.borderRadius = '12px';
        mp4Button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        mp4Button.style.cursor = 'pointer';
        mp4Button.style.zIndex = '9999';
        mp4Button.style.fontSize = '14px';
        mp4Button.style.fontWeight = 'bold';
        mp4Button.style.transition = 'background-color 0.3s ease, color 0.3s ease, transform 0.2s ease';
        mp4Button.style.opacity = '0.3'; // Default opacity

        // Add hover effect
        mp4Button.addEventListener('mouseover', () => {
            mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.5)'; // Semi-transparent blue on hover
            mp4Button.style.color = 'rgba(255, 255, 255, 1)'; // Full opacity text
            mp4Button.style.transform = 'scale(1.05)';
            mp4Button.style.opacity = '1'; // Full opacity
            triangle.style.opacity = '0'; // Reduced opacity

        });

        mp4Button.addEventListener('mouseout', () => {
            mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.2)'; // Transparent blue
            mp4Button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
            mp4Button.style.transform = 'scale(1)';
            mp4Button.style.opacity = '0.3'; // Reduced opacity
            triangle.style.opacity = '0.2'; // Reduced opacity
        });

        // Append MP4 button to the document body
        document.body.appendChild(mp4Button);

        // Add click event to the MP4 button
        mp4Button.addEventListener('click', function() {
            window.open(finalUrl, '_blank', 'noopener,noreferrer');
        });

        // Create the Video button
        const videoButton = document.createElement('button');
        videoButton.id = 'vimeo-video-button';
        videoButton.textContent = 'Video';
        videoButton.style.position = 'fixed';
        videoButton.style.top = '55px';
        videoButton.style.right = '20px';
        videoButton.style.padding = '8px 12px';
        videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.2)'; // Transparent green
        videoButton.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
        videoButton.style.border = 'none';
        videoButton.style.borderRadius = '12px';
        videoButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
        videoButton.style.cursor = 'pointer';
        videoButton.style.zIndex = '9999';
        videoButton.style.fontSize = '14px';
        videoButton.style.fontWeight = 'bold';
        videoButton.style.transition = 'background-color 0.3s ease, color 0.3s ease, transform 0.2s ease';
        videoButton.style.opacity = '0.3'; // Default opacity

        // Add hover effect
        videoButton.addEventListener('mouseover', () => {
            videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.5)'; // Semi-transparent green on hover
            videoButton.style.color = 'rgba(255, 255, 255, 1)'; // Full opacity text
            videoButton.style.transform = 'scale(1.05)';
            videoButton.style.opacity = '1'; // Full opacity
            triangle.style.opacity = '0'; // Reduced opacity
        });

        videoButton.addEventListener('mouseout', () => {
            videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.2)'; // Transparent green
            videoButton.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
            videoButton.style.transform = 'scale(1)';
            videoButton.style.opacity = '0.3'; // Reduced opacity
            triangle.style.opacity = '0.2'; // Reduced opacity
        });

        // Append Video button to the document body
        document.body.appendChild(videoButton);

        // Add click event to the Video button
        videoButton.addEventListener('click', function() {
            // Toggle visibility of resolution buttons
            const resolutionButtons = document.querySelectorAll('.vimeo-resolution-button');
            const isVisible = resolutionButtons.length > 0;
            if (isVisible) {
                resolutionButtons.forEach(button => button.remove());
            } else {
                createResolutionButtons();
            }
        });

        // Create the triangle above the MP4 button
        const triangle = document.createElement('div');
        triangle.id = 'vimeo-triangle';
        triangle.style.position = 'fixed';
        triangle.style.top = '8px';
        triangle.style.right = '40px';
        triangle.style.width = '0';
        triangle.style.height = '0';
        triangle.style.borderLeft = '8px solid transparent';
        triangle.style.borderRight = '8px solid transparent';
        triangle.style.borderTop = '8px solid black'; // Inverted triangle (downward-pointing)
        triangle.style.opacity = '0.7';
        triangle.style.transition = 'opacity 0.2s ease';
        document.body.appendChild(triangle);
    }

    function createResolutionButtons() {
        // Remove any existing resolution buttons
        const existingResolutionButtons = document.querySelectorAll('.vimeo-resolution-button');
        existingResolutionButtons.forEach(button => button.remove());

        // Get the video button's position
        const videoButton = document.getElementById('vimeo-video-button');
        const videoButtonRect = videoButton.getBoundingClientRect();
        const videoButtonTop = videoButtonRect.bottom;

        // Get all resolution IDs
        let currentTop = videoButtonTop + 10; // Initial vertical offset

        for (let id in window.resolutions) {
            if (window.resolutions.hasOwnProperty(id)) {
                const resolution = window.resolutions[id];

                // Create a resolution button
                const button = document.createElement('button');
                button.textContent = resolution;
                button.className = 'vimeo-resolution-button';
                button.style.position = 'fixed';
                button.style.top = `${currentTop}px`; // Adjust position to align with the video button
                button.style.right = '20px';
                button.style.padding = '8px 12px';
                button.style.backgroundColor = 'rgba(40, 147, 89, 0.8)'; // Slightly different green
                button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
                button.style.border = 'none';
                button.style.borderRadius = '12px';
                button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
                button.style.cursor = 'pointer';
                button.style.zIndex = '9999';
                button.style.fontSize = '12px';
                button.style.fontWeight = 'bold';
                button.style.marginBottom = '4px'; // Space between buttons
                button.style.display = 'block'; // Stack vertically

                // Add click event to the resolution button
                button.addEventListener('click', function() {
                    const url = `${baseUrl}/parcel/video/${id}.mp4`;
                    window.open(url, '_blank', 'noopener,noreferrer');
                });

                // Append button to the document body
                document.body.appendChild(button);

                // Update the position for the next button
                currentTop += button.offsetHeight + 4; // Add space between buttons
            }
        }
    }

    function removeExistingButtons() {
        const buttonsToRemove = [
            'vimeo-mp4-button',
            'vimeo-video-button',
            'vimeo-triangle'
        ];
        buttonsToRemove.forEach(id => {
            const button = document.getElementById(id);
            if (button) {
                button.remove();
            }
        });
    }

    // Intercept XMLHttpRequests
    (function(open, send) {
        XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
            this._url = url;
            open.call(this, method, url, async, user, password);
        };
        XMLHttpRequest.prototype.send = function(body) {
            this.addEventListener('load', function() {
                if (this.responseType === 'text' || this.responseType === '') {
                    handleUrl(this._url, this.responseText);
                }
            });
            send.call(this, body);
        };
    })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send);

    // Intercept Fetch API calls
    (function(fetch) {
        window.fetch = function() {
            return fetch.apply(this, arguments).then(response => {
                let url = response.url;
                if (response.headers.get('Content-Type') === 'application/json' || response.headers.get('Content-Type') === null) {
                    return response.text().then(text => {
                        handleUrl(url, text);
                    });
                }
                return response;
            });
        };
    })(window.fetch);

})();