BetterYTM.user.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // ==UserScript==
  2. // @name BetterYTM
  3. // @name:de BetterYTM
  4. // @namespace https://github.com/Sv443/BetterYTM#readme
  5. // @version 0.2.0
  6. // @license MIT
  7. // @author Sv443
  8. // @copyright Sv443 <[email protected]> (https://github.com/Sv443)
  9. // @description Improvements for YouTube Music
  10. // @description:de Verbesserungen für YouTube Music
  11. // @match https://music.youtube.com/*
  12. // @match https://www.youtube.com/*
  13. // @icon https://www.google.com/s2/favicons?domain=music.youtube.com
  14. // @grant none
  15. // @run-at document-start
  16. // @connect self
  17. // @connect *.youtube.com
  18. // @downloadURL https://raw.githubusercontent.com/Sv443/BetterYTM/main/BetterYTM.user.js
  19. // @updateURL https://raw.githubusercontent.com/Sv443/BetterYTM/main/BetterYTM.user.js
  20. // ==/UserScript==
  21. /* Disclaimer: I am not affiliated with YouTube, Google, Alphabet or anyone else */
  22. /* C&D this Susan 🖕 */
  23. /*
  24. █▀▀▀█ ▄▄▄ █ █ ▀ ▄▄▄ ▄▄▄▄ ▄▄▄
  25. ▀▀▬▄▄ █▄█ █▀ █▀ ▀█ █ █ █ ▄▄ █▄▄ ▀
  26. █▄▄▄█ █▄▄ █▄▄ █▄▄ ▄█▄ █ █ █▄▄█ ▄▄█ ▄
  27. */
  28. /**
  29. * This is where you can enable or disable features
  30. * If this userscript ever becomes something I might add like a menu to toggle these
  31. */
  32. const features = Object.freeze({
  33. /** Whether arrow keys should skip forwards and backwards by 10 seconds */
  34. arrowKeySupport: true,
  35. /** Whether to add a button or key combination (TODO) to switch between the YouTube and YouTube Music pages */
  36. switchBetweenSites: true,
  37. // /** The theme color - accepts any CSS color value - default is "#ff0000" */
  38. // themeColor: "#0f0",
  39. });
  40. //#MARKER types
  41. /** @typedef {"yt"|"ytm"} Domain */
  42. //#MARKER init
  43. const info = Object.freeze({
  44. name: GM.info.script.name, // eslint-disable-line no-undef
  45. version: GM.info.script.version, // eslint-disable-line no-undef
  46. namespace: GM.info.script.namespace, // eslint-disable-line no-undef
  47. });
  48. function init()
  49. {
  50. console.log(`${info.name} v${info.version} - ${info.namespace}`);
  51. document.addEventListener("DOMContentLoaded", onDomLoad);
  52. }
  53. //#MARKER events
  54. /**
  55. * Called when the DOM has finished loading
  56. */
  57. function onDomLoad()
  58. {
  59. const domain = getDomain();
  60. if(features.arrowKeySupport && domain === "ytm")
  61. document.addEventListener("keydown", onKeyDown);
  62. if(features.switchBetweenSites)
  63. initSiteSwitch(domain);
  64. // if(features.themeColor != "#f00" && features.themeColor != "#ff0000")
  65. // applyTheme();
  66. }
  67. //#MARKER features
  68. //#SECTION arrow key skip
  69. /**
  70. * Called when the user presses keys
  71. * @param {KeyboardEvent} evt
  72. */
  73. function onKeyDown(evt)
  74. {
  75. if(["ArrowLeft", "ArrowRight"].includes(evt.code))
  76. {
  77. switch(evt.code)
  78. {
  79. case "ArrowLeft":
  80. // ripped this stuff from the console, most of these are probably unnecessary but this was finnicky af and I am sick and tired of trial and error
  81. document.body.dispatchEvent(new KeyboardEvent("keydown", {
  82. altKey: false,
  83. bubbles: true,
  84. cancelBubble: false,
  85. cancelable: true,
  86. charCode: 0,
  87. code: "KeyH",
  88. composed: true,
  89. ctrlKey: false,
  90. currentTarget: null,
  91. defaultPrevented: evt.defaultPrevented,
  92. explicitOriginalTarget: document.body,
  93. isTrusted: true,
  94. key: "h",
  95. keyCode: 72,
  96. metaKey: false,
  97. originalTarget: document.body,
  98. repeat: false,
  99. shiftKey: false,
  100. srcElement: document.body,
  101. target: document.body,
  102. type: "keydown",
  103. view: window,
  104. which: 72,
  105. }));
  106. break;
  107. case "ArrowRight":
  108. // ripped this stuff from the console, most of these are probably unnecessary but this was finnicky af and I am sick and tired of trial and error
  109. document.body.dispatchEvent(new KeyboardEvent("keydown", {
  110. altKey: false,
  111. bubbles: true,
  112. cancelBubble: false,
  113. cancelable: true,
  114. charCode: 0,
  115. code: "KeyL",
  116. composed: true,
  117. ctrlKey: false,
  118. currentTarget: null,
  119. defaultPrevented: evt.defaultPrevented,
  120. explicitOriginalTarget: document.body,
  121. isTrusted: true,
  122. key: "l",
  123. keyCode: 76,
  124. metaKey: false,
  125. originalTarget: document.body,
  126. repeat: false,
  127. shiftKey: false,
  128. srcElement: document.body,
  129. target: document.body,
  130. type: "keydown",
  131. view: window,
  132. which: 76,
  133. }));
  134. break;
  135. default:
  136. console.warn("Unknown key", evt.code);
  137. break;
  138. }
  139. }
  140. }
  141. //#SECTION site switch
  142. /**
  143. * Initializes the site switch feature
  144. * @param {Domain} domain
  145. */
  146. function initSiteSwitch(domain)
  147. {
  148. // TODO:
  149. // - create button element
  150. // - bind event to switch href
  151. //
  152. // extra features:
  153. // - keep video time
  154. const button = document.createElement("button");
  155. if(domain === "yt")
  156. {
  157. button.on("click", switchSite(domain));
  158. }
  159. else if(domain === "ytm")
  160. {
  161. button.on("click", switchSite(domain));
  162. }
  163. }
  164. /**
  165. * Switches to the other site (between YT and YTM)
  166. * @param {Domain} domain
  167. */
  168. function switchSite(domain)
  169. {
  170. let subdomain;
  171. if(domain === "yt")
  172. subdomain = "music";
  173. else if(domain === "ytm")
  174. subdomain = "www";
  175. if(!subdomain)
  176. throw new TypeError(`Unrecognized domain '${domain}'`);
  177. const { pathname, search } = new URL(location.href);
  178. const url = `https://${subdomain}.youtube.com${pathname}${search}`;
  179. console.info(`BetterYTM - switching to domain '${domain}' at ${url}`);
  180. location.href = url;
  181. }
  182. //# SECTION theme
  183. // /**
  184. // * Applies the set theme color
  185. // */
  186. // function applyTheme()
  187. // {
  188. // const formatRegex = /^(\d{3}){1,2}$/;
  189. // const color = features.themeColor.match(formatRegex) ? `#${color}` : color;
  190. // /**
  191. // * A list of changes to be made to the page to apply the theme color
  192. // */
  193. // const themeChanges = [
  194. // {
  195. // elem: document.querySelector("#progressContainer > #primaryProgress"),
  196. // prop: "background",
  197. // important: false,
  198. // },
  199. // {
  200. // elem: document.querySelector(),
  201. // prop: "",
  202. // important: false,
  203. // },
  204. // {
  205. // elem: document.querySelector(),
  206. // prop: "",
  207. // important: false,
  208. // },
  209. // ];
  210. // themeChanges.forEach(change => {
  211. // if(change.elem)
  212. // {
  213. // const value = change.important === true ? `${color} !important` : color;
  214. // change.elem.style[change.prop] = value;
  215. // }
  216. // });
  217. // }
  218. //#MARKER other
  219. /**
  220. * Returns the current domain as a string representation
  221. * @returns {Domain}
  222. */
  223. function getDomain()
  224. {
  225. // TODO: maybe improve this
  226. return location.href.toLowerCase().includes("music.youtube") ? "ytm" : "yt"; // other cases are caught by `@match`es above
  227. }
  228. (() => init())(); // call init() when file is loaded