Invidious save video progress

Saves video progress on invidio.us and adds some more functonality

目前为 2019-05-03 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Invidious save video progress
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.1
  5. // @description Saves video progress on invidio.us and adds some more functonality
  6. // @author Noruf
  7. // @match https://invidio.us/*
  8. // @match https://invidious.snopyta.org/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14. const timestamps = localStorage.timestamps ? JSON.parse(localStorage.timestamps) : {};
  15. let search = window.location.search;
  16. const isVideoPage = window.location.pathname.includes('watch');
  17. const videoId = isVideoPage? window.location.search.match(/v=(.*?)(&|$)/)[1]: ' ';
  18. var url = new URL(window.location.href);
  19. var time = url.searchParams.get("t");
  20. if(!time&&timestamps[videoId]){
  21. search = replaceQueryParam('t', timestamps[videoId], search);
  22. window.location.replace(window.location.pathname + search);
  23. }
  24. onReadyEvent(addHistoryButton);
  25. onReadyEvent(changeLinks);
  26. if(isVideoPage) onReadyEvent(videoProgressMain);
  27. onReadyEvent(addCopyYoutubeLinkButton);
  28.  
  29. function replaceQueryParam(param, newval, search) {
  30. const regex = new RegExp("([?;&])" + param + "[^&;]*[;&]?");
  31. const query = search.replace(regex, "$1").replace(/&$/, '');
  32. return (query.length > 2 ? query + "&" : "?") + (newval ? param + "=" + newval : '');
  33. }
  34.  
  35. function onReadyEvent(callback){
  36. // in case the document is already rendered
  37. if (document.readyState!='loading') callback();
  38. // modern browsers
  39. else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
  40. // IE <= 8
  41. else {document.attachEvent('onreadystatechange', function(){
  42. if (document.readyState=='complete') callback();
  43. });}
  44. }
  45.  
  46. function addCopyYoutubeLinkButton(){
  47. if(!isVideoPage)return;
  48. const a = document.querySelector('a[href*="youtube"');
  49. const p = document.createElement("a");
  50. p.appendChild(document.createTextNode(" copy"));
  51. a.parentElement.append(p);
  52. p.onclick = () => {
  53. copyToClipboard(a.href);
  54. };
  55. const altP = document.createElement("p");
  56. const altLink = document.createElement("a");
  57. altLink.appendChild(document.createTextNode("Alternate source"));
  58. altLink.href = `https://invidious.snopyta.org/watch?v=${videoId}`;
  59. altP.appendChild(altLink);
  60. const ul = p.parentElement.parentElement;
  61. ul.insertBefore(altP,ul.childNodes[2]);
  62. }
  63.  
  64.  
  65. function addHistoryButton(){
  66. const userfield = document.querySelector('.user-field');
  67. const newdiv = document.createElement('div');
  68. newdiv.className = 'pure-u-1-4';
  69. userfield.prepend(newdiv);
  70. const anchor = document.createElement('a');
  71. anchor.href = '/feed/history';
  72. anchor.className = 'pure-menu-heading';
  73. newdiv.append(anchor);
  74. const i = document.createElement('i');
  75. i.className = 'icon ion-md-time';
  76. anchor.append(i);
  77. }
  78. function changeLinks(){
  79. const thumbnails = document.querySelectorAll('div.thumbnail');
  80. thumbnails.forEach(t =>{
  81. const a = t.parentElement;
  82. const href = a.href;
  83. if(!href.includes("watch"))return;
  84. const id = href.match(/v=(.*?)(&|$)/)[1];
  85. if(timestamps[id]){
  86. a.href = `${href}&t=${timestamps[id]}s`;
  87. }
  88. if(isVideoPage)return;
  89. const YT = href.replace(window.location.host,'youtube.com');
  90. const copy = document.createElement("a");
  91. copy.appendChild(document.createTextNode("copy"));
  92. const open = document.createElement("a");
  93. open.href = YT;
  94. open.appendChild(document.createTextNode("open"));
  95. const div = document.createElement('h5');
  96. div.style['text-align'] = 'right';
  97. div.style['margin-top'] = '-5%';
  98. div.append('YT link: ',copy,' ',open);
  99. a.parentElement.append(div);
  100. copy.onclick = () => {
  101. copyToClipboard(YT);
  102. };
  103. });
  104. }
  105.  
  106. function videoProgressMain (){
  107. const player = document.querySelector('video');
  108. player.onpause = () => {saveProgress(false)};
  109. window.addEventListener('beforeunload', function (e) {
  110. saveProgress(false);
  111. e.returnValue = ''; // Chrome requires returnValue to be set.
  112. });
  113. const saveToClipboard = document.createElement("BUTTON");
  114. saveToClipboard.className = "pure-button";
  115. saveToClipboard.appendChild(document.createTextNode("Save To Clipboard"))
  116. document.querySelector('#subscribe').parentElement.appendChild(saveToClipboard);
  117. const message = document.createElement("span");
  118. document.querySelector('#genre').parentElement.appendChild(message);
  119. saveToClipboard.onclick = () => {
  120. saveProgress(true);
  121. }
  122. function saveProgress(doCopy){
  123. const time = Math.floor(document.querySelector('video').currentTime);
  124. if(isNaN(player.duration))return;
  125. if(doCopy){
  126. copyToClipboard(getURL(time));
  127. }
  128. timestamps[videoId] = time;
  129. if(time < 60 || player.duration - time < 60) {
  130. delete timestamps[videoId];
  131. message.innerHTML = `Timestamp not saved!`;
  132. } else{
  133. message.innerHTML = `Saved at ${convertSeconds(time)}`;
  134. }
  135. history.replaceState( {} , '', replaceQueryParam('t',time,window.location.pathname + window.location.search));
  136. localStorage.timestamps = JSON.stringify(timestamps);
  137. }
  138.  
  139. function getURL(seconds){
  140. return `https://invidio.us/watch?v=${videoId}&t=${seconds}s`;
  141. }
  142. function convertSeconds(seconds){
  143. return new Date(seconds * 1000).toISOString().substr(11, 8);
  144. }
  145. }
  146. function copyToClipboard(text) {
  147. const textArea = document.createElement("textarea");
  148. textArea.value = text;
  149. textArea.style.position = 'fixed';
  150. textArea.style.top = 0;
  151. textArea.style.left = 0;
  152. textArea.style.width = '2em';
  153. textArea.style.height = '2em';
  154. textArea.style.padding = 0;
  155. textArea.style.border = 'none';
  156. textArea.style.outline = 'none';
  157. textArea.style.boxShadow = 'none';
  158. textArea.style.background = 'transparent';
  159. document.body.appendChild(textArea);
  160. textArea.focus();
  161. textArea.select();
  162. try {
  163. const successful = document.execCommand('copy');
  164. const msg = successful ? 'successful' : 'unsuccessful';
  165. console.log('Copying text command was ' + msg);
  166. } catch (err) {
  167. console.error('Oops, unable to copy', err);
  168. }
  169. document.body.removeChild(textArea);
  170. }
  171. })();