Vimeo Video Downloader

Provides buttons to extract the video

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);

})();