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