ThisVid Download Button

Adds a download buttons to ThisVid video pages.

  1. // ==UserScript==
  2. // @name ThisVid Download Button
  3. // @namespace https://thisvid.com/
  4. // @version 2.0.1
  5. // @description Adds a download buttons to ThisVid video pages.
  6. // @author persistentScripter
  7. // @license MIT
  8. // @include http*://thisvid.com/*
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js
  10. // @run-at document-end
  11. // ==/UserScript==
  12.  
  13. const wait = (ms) => {
  14. return new Promise((resolve) => setTimeout(resolve, ms));
  15. }
  16.  
  17. const snakeCase = (str) => {
  18. str = str.replace(/\W+/g, ' ').toLowerCase().split(' ').join('_');
  19.  
  20. return str;
  21. };
  22.  
  23. // Downloads a video via fetch() so we can save the file with the appropriate file name.
  24. let getVid = async (fileURL) => {
  25. let controller = new AbortController();
  26. let {signal} = controller;
  27.  
  28. const {title} = document;
  29.  
  30. document.title = `[↓] ${title.replace(/\[↓\]/g, '')}`;
  31.  
  32. let fileName = snakeCase(title.replace(/ThisVid\.com|at ThisVid tube/, '').trim());
  33.  
  34. let resp = await fetch(fileURL, {
  35. method: 'GET',
  36. redirect: 'follow',
  37. signal,
  38. });
  39.  
  40. // First request will just be a redirect. Catch it, abort, and continue.
  41. if (resp.redirected) {
  42. controller.abort();
  43.  
  44. controller = new AbortController();
  45. signal = controller.signal;
  46.  
  47. resp = await fetch(resp.url, {
  48. method: 'GET',
  49. signal,
  50. });
  51. }
  52.  
  53. let blob = await resp.blob();
  54.  
  55. try {
  56. url = window.URL.createObjectURL(blob);
  57. } catch (e) {
  58. url = resp.url;
  59.  
  60. return url;
  61. }
  62.  
  63. const a = document.createElement('a');
  64.  
  65. document.title = `[✓] ${title.replace(/\[✓\]/g, '')}`;
  66.  
  67. a.style.display = 'none';
  68. a.href = url;
  69. a.download = fileName;
  70.  
  71. document.body.appendChild(a);
  72.  
  73. a.click();
  74.  
  75. window.URL.revokeObjectURL(url);
  76.  
  77. return true;
  78. };
  79.  
  80. const filterPrivateVideos = () => {
  81. let priv = Array.from(document.querySelectorAll('.icon-private'));
  82.  
  83. for (let i = 0, len = priv.length; i < len; i++) {
  84. let item = priv[i];
  85.  
  86. let container = item.parentNode.parentNode.parentNode;
  87. let vid = item.parentNode.parentNode;
  88.  
  89. container.removeChild(vid);
  90. }
  91. }
  92.  
  93. const addDownloadButton = async () => {
  94. let flagContainer = document.querySelector('#flagging_container');
  95.  
  96. if (!flagContainer) return;
  97.  
  98. let video = document.querySelector('video');
  99. let fileURL;
  100.  
  101. // Video element is now inserted into DOM after video starts playing, this simulates a play click and then pauses the video.
  102. if (!video) {
  103. let playButton = document.querySelector('#kt_player > div.fp-player > div.fp-ui > div.fp-controls.fade > a.fp-play');
  104.  
  105. playButton.click();
  106.  
  107. await wait(500);
  108.  
  109. video = document.querySelector('video');
  110. }
  111.  
  112. video.pause();
  113.  
  114. fileURL = video.src;
  115.  
  116. let li = document.createElement('li');
  117. let a = document.createElement('a');
  118. let span = document.createElement('span');
  119.  
  120. li.classList.add('share_button');
  121. li.appendChild(a);
  122.  
  123. a.classList.add('__dl');
  124. a.href = fileURL;
  125.  
  126. Object.assign(span.style, {
  127. color: '#fff',
  128. fontSize: '32px',
  129. });
  130.  
  131. a.innerHTML += `
  132. <span class="tooltip">download</span>
  133. `;
  134.  
  135. span.innerText = '↓';
  136.  
  137. a.appendChild(span);
  138.  
  139. a.addEventListener('click', async (e) => {
  140. e.preventDefault();
  141. e.stopPropagation();
  142.  
  143. try {
  144. await getVid(fileURL);
  145. } catch (e) {
  146. let {title} = document;
  147.  
  148. document.title = `[✗] ${title.replace(/\[✗\]/g, '')}`;
  149.  
  150. window.open(fileURL);
  151. }
  152. });
  153.  
  154. flagContainer.appendChild(li);
  155. }
  156.  
  157. const sortVideos = () => {
  158. let els = Array.from(
  159. document.querySelectorAll('body > div.wrapper > div > div > section > div > a > span.thumb > span.percent')
  160. );
  161. let group = [];
  162. let globalContainer = null;
  163.  
  164. if (!els.length) {
  165. els = Array.from(
  166. document.querySelectorAll('body > div.wrapper > div > div > section > div > div > a > span.thumb > span.percent')
  167. );
  168. }
  169.  
  170. if (!els.length) return;
  171.  
  172. for (let i = 0, len = els.length; i < len; i++) {
  173. let el = els[i];
  174. let int = parseInt(el.innerText.split('%')[0]);
  175.  
  176. el = el.parentNode.parentNode;
  177. let container = el.parentNode;
  178.  
  179. if (i === 0) {
  180. globalContainer = container;
  181. }
  182.  
  183. try {
  184. container.removeChild(el);
  185. } catch (e) {
  186. console.log('fail', {el, container});
  187. continue;
  188. }
  189.  
  190. group.push({
  191. el,
  192. int,
  193. });
  194. }
  195.  
  196. group = _.orderBy(group, 'int', 'desc');
  197.  
  198. for (let i = 0, len = group.length; i < len; i++) {
  199. let {el} = group[i];
  200.  
  201. globalContainer.appendChild(el);
  202. }
  203. }
  204.  
  205. window.addEventListener('load', () => {
  206. setTimeout(filterPrivateVideos, 100);
  207. setTimeout(addDownloadButton, 100);
  208. setTimeout(sortVideos, 200);
  209. });