yt-url-at-time

On youtube, use alt+` to set the url to the current timestamp, for easy bookmarking

  1. // ==UserScript==
  2. // @name yt-url-at-time
  3. // @namespace mechalynx/yt-url-at-time
  4. // @license MIT
  5. // @grant none
  6. // @description On youtube, use alt+` to set the url to the current timestamp, for easy bookmarking
  7. // @include https://www.youtube.com/*
  8. // @version 0.2.7
  9. // @copyright 2017, MechaLynx (https://github.com/MechaLynx)
  10. // @run-at document-idle
  11. // @author MechaLynx
  12. // ==/UserScript==
  13. // jshint esversion: 6
  14.  
  15. // Matches time hashes for the purpose of removing them
  16. // note that I don't like my regexp here...
  17. var re_timehash = /#t=(?:[0-9]*\.?[0-9]*|(?:[0-9]*(?:h|m|s))*)*/g;
  18.  
  19. // `video` element utility
  20. var video = {
  21. get element() {
  22. return document.querySelector('#movie_player video');
  23. },
  24. get timehash() {
  25. var secs = this.element.currentTime;
  26. return '#t=' + [(h = ~~(secs / 3600)) && h + 'h' || null,
  27. (m = ~~(secs % 3600 / 60)) && m + 'm' || null,
  28. (s = ~~(secs % 3600 % 60)) && s + 's'].join('');
  29. },
  30. get plaintimehash() {
  31. return '#t=' + this.element.currentTime;
  32. },
  33. get notimehash() {
  34. return window.location.origin +
  35. window.location.pathname +
  36. window.location.search +
  37. window.location.hash.replace(re_timehash, '');
  38. }
  39. };
  40.  
  41. // Keep looking for the time indicator span, until it's found
  42. // The `load` event is insufficient
  43. var wait_for_page = window.setInterval(function(){
  44. var current_time_element = document.querySelector('.ytp-time-current');
  45. if (current_time_element){
  46. window.clearInterval(wait_for_page);
  47.  
  48. // Add CSS for time indicator span
  49. let time_style = document.createElement('style');
  50. time_style.setAttribute('name', "yt-url-at-time");
  51. time_style.innerHTML = `
  52. .url-at-time-element-hover:hover{
  53. cursor: pointer;
  54. }
  55. .url-at-time-clipboard-helper{
  56. position: absolute;
  57. top: 0;
  58. left: 0;
  59. padding: none;
  60. margin: none;
  61. border: none;
  62. width: 0;
  63. height: 0;
  64. }
  65. `;
  66. document.body.appendChild(time_style);
  67.  
  68. // Toggle the class so that it doesn't look clickable
  69. // during ads, which would be confusing
  70. current_time_element.onmouseover = function(){
  71. if (document.querySelector('.videoAdUi')){
  72. current_time_element.classList.remove('url-at-time-element-hover');
  73. }else{
  74. current_time_element.classList.add('url-at-time-element-hover');
  75. }
  76. };
  77.  
  78. current_time_element.addEventListener('click', function(e){
  79. if (e.altKey){
  80. hashmodifier(true);
  81. } else {
  82. hashmodifier(false);
  83. }
  84.  
  85. if (e.ctrlKey){
  86. copy_url_to_clipboard();
  87. }
  88. });
  89. }
  90. }, 1000);
  91.  
  92. // Add the timestamp to the URL
  93. var hashmodifier = function(precise=false){
  94. if ( location.href.match(/.*watch.*/) && document.querySelector('.videoAdUi') === null){
  95. precise ? history.replaceState(false, false, video.notimehash + video.plaintimehash) : history.replaceState(false, false, video.notimehash + video.timehash);
  96. }
  97. };
  98.  
  99. var copy_url_to_clipboard = function(attempt_to_restore=false){
  100. // Current focus and selection cannot be restored
  101. // since clicking on the timer causes the movie player to be focused
  102. // clearing the selection and changing the active element before we arrive here
  103. // However, attempting to restore them is meaningful if called through a hotkey
  104. if (attempt_to_restore){
  105. var selection = document.getSelection();
  106. var current_selection = selection.getRangeAt(0);
  107. var current_focus = document.activeElement;
  108. }
  109.  
  110. // Add invisible textarea to allow copying the generated URL to clipboard
  111. let clipboard_helper = document.createElement('textarea');
  112. clipboard_helper.classList.add('url-at-time-clipboard-helper');
  113. document.body.appendChild(clipboard_helper);
  114.  
  115. clipboard_helper.value = window.location.href;
  116. clipboard_helper.select();
  117. clipboard_helper.setSelectionRange(0, clipboard_helper.value.length);
  118. document.execCommand('copy');
  119.  
  120. document.body.removeChild(clipboard_helper);
  121.  
  122. if (attempt_to_restore){
  123. current_focus.focus();
  124.  
  125. // https://gist.github.com/dantaex/543e721be845c18d2f92652c0ebe06aa
  126. selection.empty();
  127. selection.addRange(current_selection);
  128. }
  129. };
  130.  
  131. var _alt=false;
  132. var _q=false;
  133. // Listen for the hotkey
  134. document.addEventListener('keydown', z => {
  135. // if you want to change the hotkey
  136. // you can use this: http://mechalynx.github.io/keypress/
  137. // or another tester if you don't like this one
  138. if (z.code === 'KeyQ'){
  139. _q=true;
  140. }
  141. if (z.altKey && z.code === 'Backquote'){
  142. hashmodifier(_alt);
  143. _alt=true;
  144. }
  145. if (_q && _alt){
  146. copy_url_to_clipboard(true);
  147. }
  148. });
  149.  
  150. document.addEventListener('keyup', z => {
  151. if(!z.altKey){
  152. _alt=false;
  153. }
  154. if(z.code === "KeyQ"){
  155. _q=false;
  156. }
  157. });