Vimeo Video Downloader

Provides buttons to extract the video

当前为 2024-08-14 提交的版本,查看 最新版本

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