Vimeo Video Downloader

Provides buttons to extract the video

  1. // ==UserScript==
  2. // @name Vimeo Video Downloader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Provides buttons to extract the video
  6. // @author Tristan Reeves
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. let baseUrl = '';
  15. let finalUrl = '';
  16.  
  17. // Function to handle the URL and response
  18. async function handleUrl(url, responseText) {
  19. if (url.includes("playlist.json")) {
  20. // Extract the base URL up to "/v2"
  21. baseUrl = url.split('/v2')[0];
  22.  
  23. // Parse the JSON response to extract the audio ID
  24. try {
  25. const data = JSON.parse(responseText);
  26.  
  27. // Extract and log video resolutions
  28. if (data.video) {
  29. for (let key in data.video) {
  30. if (data.video[key] && data.video[key].height) {
  31. let height = data.video[key].height;
  32. let id = data.video[key].id || 'No ID';
  33.  
  34. let resolution = height === 240 ? '240px' : `${height}px`;
  35. const RESurl = `${baseUrl}/parcel/video/${id}.mp4`;
  36. console.log(`Resolution: ${resolution}, url: ${RESurl}`);
  37.  
  38. // Save resolution data for use
  39. if (!window.resolutions) {
  40. window.resolutions = {};
  41. }
  42. window.resolutions[id] = resolution;
  43. }
  44. }
  45. }
  46.  
  47. if (data.audio && data.audio.length > 0) {
  48. for (let audioItem of data.audio) {
  49. if (audioItem.id) {
  50. let audioId = audioItem.id;
  51.  
  52. // Construct the final URL
  53. finalUrl = `${baseUrl}/parcel/video/${audioId}.mp4`;
  54. console.log("Audio+ URL: ", finalUrl);
  55.  
  56. // Create or update the MP4 button
  57. createMp4Button(finalUrl);
  58.  
  59. break; // Stop after the first valid audio ID
  60. }
  61. }
  62. }
  63. } catch (e) {
  64. console.error("Error parsing JSON response:", e);
  65. }
  66. }
  67. }
  68.  
  69. function createMp4Button(finalUrl) {
  70. // Remove any existing buttons to avoid duplicates
  71. removeExistingButtons();
  72.  
  73. // Create the MP4 button
  74. const mp4Button = document.createElement('button');
  75. mp4Button.id = 'vimeo-mp4-button';
  76. mp4Button.textContent = 'Mp4';
  77. mp4Button.style.position = 'fixed';
  78. mp4Button.style.top = '20px';
  79. mp4Button.style.right = '20px';
  80. mp4Button.style.padding = '8px 12px';
  81. mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.2)'; // Transparent blue
  82. mp4Button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
  83. mp4Button.style.border = 'none';
  84. mp4Button.style.borderRadius = '12px';
  85. mp4Button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
  86. mp4Button.style.cursor = 'pointer';
  87. mp4Button.style.zIndex = '9999';
  88. mp4Button.style.fontSize = '14px';
  89. mp4Button.style.fontWeight = 'bold';
  90. mp4Button.style.transition = 'background-color 0.3s ease, color 0.3s ease, transform 0.2s ease';
  91. mp4Button.style.opacity = '0.3'; // Default opacity
  92.  
  93. // Add hover effect
  94. mp4Button.addEventListener('mouseover', () => {
  95. mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.5)'; // Semi-transparent blue on hover
  96. mp4Button.style.color = 'rgba(255, 255, 255, 1)'; // Full opacity text
  97. mp4Button.style.transform = 'scale(1.05)';
  98. mp4Button.style.opacity = '1'; // Full opacity
  99. triangle.style.opacity = '0'; // Reduced opacity
  100.  
  101. });
  102.  
  103. mp4Button.addEventListener('mouseout', () => {
  104. mp4Button.style.backgroundColor = 'rgba(0, 123, 255, 0.2)'; // Transparent blue
  105. mp4Button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
  106. mp4Button.style.transform = 'scale(1)';
  107. mp4Button.style.opacity = '0.3'; // Reduced opacity
  108. triangle.style.opacity = '0.2'; // Reduced opacity
  109. });
  110.  
  111. // Append MP4 button to the document body
  112. document.body.appendChild(mp4Button);
  113.  
  114. // Add click event to the MP4 button
  115. mp4Button.addEventListener('click', function() {
  116. window.open(finalUrl, '_blank', 'noopener,noreferrer');
  117. });
  118.  
  119. // Create the Video button
  120. const videoButton = document.createElement('button');
  121. videoButton.id = 'vimeo-video-button';
  122. videoButton.textContent = 'Video';
  123. videoButton.style.position = 'fixed';
  124. videoButton.style.top = '55px';
  125. videoButton.style.right = '20px';
  126. videoButton.style.padding = '8px 12px';
  127. videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.2)'; // Transparent green
  128. videoButton.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
  129. videoButton.style.border = 'none';
  130. videoButton.style.borderRadius = '12px';
  131. videoButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
  132. videoButton.style.cursor = 'pointer';
  133. videoButton.style.zIndex = '9999';
  134. videoButton.style.fontSize = '14px';
  135. videoButton.style.fontWeight = 'bold';
  136. videoButton.style.transition = 'background-color 0.3s ease, color 0.3s ease, transform 0.2s ease';
  137. videoButton.style.opacity = '0.3'; // Default opacity
  138.  
  139. // Add hover effect
  140. videoButton.addEventListener('mouseover', () => {
  141. videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.5)'; // Semi-transparent green on hover
  142. videoButton.style.color = 'rgba(255, 255, 255, 1)'; // Full opacity text
  143. videoButton.style.transform = 'scale(1.05)';
  144. videoButton.style.opacity = '1'; // Full opacity
  145. triangle.style.opacity = '0'; // Reduced opacity
  146. });
  147.  
  148. videoButton.addEventListener('mouseout', () => {
  149. videoButton.style.backgroundColor = 'rgba(40, 167, 69, 0.2)'; // Transparent green
  150. videoButton.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
  151. videoButton.style.transform = 'scale(1)';
  152. videoButton.style.opacity = '0.3'; // Reduced opacity
  153. triangle.style.opacity = '0.2'; // Reduced opacity
  154. });
  155.  
  156. // Append Video button to the document body
  157. document.body.appendChild(videoButton);
  158.  
  159. // Add click event to the Video button
  160. videoButton.addEventListener('click', function() {
  161. // Toggle visibility of resolution buttons
  162. const resolutionButtons = document.querySelectorAll('.vimeo-resolution-button');
  163. const isVisible = resolutionButtons.length > 0;
  164. if (isVisible) {
  165. resolutionButtons.forEach(button => button.remove());
  166. } else {
  167. createResolutionButtons();
  168. }
  169. });
  170.  
  171. // Create the triangle above the MP4 button
  172. const triangle = document.createElement('div');
  173. triangle.id = 'vimeo-triangle';
  174. triangle.style.position = 'fixed';
  175. triangle.style.top = '8px';
  176. triangle.style.right = '40px';
  177. triangle.style.width = '0';
  178. triangle.style.height = '0';
  179. triangle.style.borderLeft = '8px solid transparent';
  180. triangle.style.borderRight = '8px solid transparent';
  181. triangle.style.borderTop = '8px solid black'; // Inverted triangle (downward-pointing)
  182. triangle.style.opacity = '0.7';
  183. triangle.style.transition = 'opacity 0.2s ease';
  184. document.body.appendChild(triangle);
  185. }
  186.  
  187. function createResolutionButtons() {
  188. // Remove any existing resolution buttons
  189. const existingResolutionButtons = document.querySelectorAll('.vimeo-resolution-button');
  190. existingResolutionButtons.forEach(button => button.remove());
  191.  
  192. // Get the video button's position
  193. const videoButton = document.getElementById('vimeo-video-button');
  194. const videoButtonRect = videoButton.getBoundingClientRect();
  195. const videoButtonTop = videoButtonRect.bottom;
  196.  
  197. // Get all resolution IDs
  198. let currentTop = videoButtonTop + 10; // Initial vertical offset
  199.  
  200. for (let id in window.resolutions) {
  201. if (window.resolutions.hasOwnProperty(id)) {
  202. const resolution = window.resolutions[id];
  203.  
  204. // Create a resolution button
  205. const button = document.createElement('button');
  206. button.textContent = resolution;
  207. button.className = 'vimeo-resolution-button';
  208. button.style.position = 'fixed';
  209. button.style.top = `${currentTop}px`; // Adjust position to align with the video button
  210. button.style.right = '20px';
  211. button.style.padding = '8px 12px';
  212. button.style.backgroundColor = 'rgba(40, 147, 89, 0.8)'; // Slightly different green
  213. button.style.color = 'rgba(255, 255, 255, 0.7)'; // Semi-transparent text
  214. button.style.border = 'none';
  215. button.style.borderRadius = '12px';
  216. button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
  217. button.style.cursor = 'pointer';
  218. button.style.zIndex = '9999';
  219. button.style.fontSize = '12px';
  220. button.style.fontWeight = 'bold';
  221. button.style.marginBottom = '4px'; // Space between buttons
  222. button.style.display = 'block'; // Stack vertically
  223.  
  224. // Add click event to the resolution button
  225. button.addEventListener('click', function() {
  226. const url = `${baseUrl}/parcel/video/${id}.mp4`;
  227. window.open(url, '_blank', 'noopener,noreferrer');
  228. });
  229.  
  230. // Append button to the document body
  231. document.body.appendChild(button);
  232.  
  233. // Update the position for the next button
  234. currentTop += button.offsetHeight + 4; // Add space between buttons
  235. }
  236. }
  237. }
  238.  
  239. function removeExistingButtons() {
  240. const buttonsToRemove = [
  241. 'vimeo-mp4-button',
  242. 'vimeo-video-button',
  243. 'vimeo-triangle'
  244. ];
  245. buttonsToRemove.forEach(id => {
  246. const button = document.getElementById(id);
  247. if (button) {
  248. button.remove();
  249. }
  250. });
  251. }
  252.  
  253. // Intercept XMLHttpRequests
  254. (function(open, send) {
  255. XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
  256. this._url = url;
  257. open.call(this, method, url, async, user, password);
  258. };
  259. XMLHttpRequest.prototype.send = function(body) {
  260. this.addEventListener('load', function() {
  261. if (this.responseType === 'text' || this.responseType === '') {
  262. handleUrl(this._url, this.responseText);
  263. }
  264. });
  265. send.call(this, body);
  266. };
  267. })(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send);
  268.  
  269. // Intercept Fetch API calls
  270. (function(fetch) {
  271. window.fetch = function() {
  272. return fetch.apply(this, arguments).then(response => {
  273. let url = response.url;
  274. if (response.headers.get('Content-Type') === 'application/json' || response.headers.get('Content-Type') === null) {
  275. return response.text().then(text => {
  276. handleUrl(url, text);
  277. });
  278. }
  279. return response;
  280. });
  281. };
  282. })(window.fetch);
  283.  
  284. })();