HIDIVE Video Toggles

Currently we only have one toggle, it's for the HIDIVE subtitles being able to be switched between Off and English with the 'C' key.

目前为 2025-03-27 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name HIDIVE Video Toggles
  3. // @namespace Violentmonkey Scripts
  4. // @version 1.02
  5. // @author Officer Erik 1K-88
  6. // @description Currently we only have one toggle, it's for the HIDIVE subtitles being able to be switched between Off and English with the 'C' key.
  7. // @license BSD 3-Clause
  8. // @match https://www.hidive.com/video/*
  9. // @grant GM.getValue
  10. // @grant GM.setValue
  11. // @grant GM.info
  12. // ==/UserScript==
  13. // Created on 3/26/2025, 10:02:12 AM GMT+0500 (CDT)
  14.  
  15. /**
  16. * Formats a date object.
  17. * @param {Date} date The Date object.
  18. * @returns The formatted string.
  19. */
  20. function formatDate(date) {
  21. const pad = (n, width = 2) => String(n).padStart(width, '0');
  22.  
  23. const year = date.getFullYear();
  24. const month = pad(date.getMonth() + 1);
  25. const day = pad(date.getDate());
  26. const hour = pad(date.getHours());
  27. const minute = pad(date.getMinutes());
  28. const second = pad(date.getSeconds());
  29. const milliseconds = pad(date.getMilliseconds(), 3);
  30.  
  31. // GMT offset in ±HHMM format
  32. const offsetMinutes = date.getTimezoneOffset();
  33. const absOffset = Math.abs(offsetMinutes);
  34. const offsetSign = offsetMinutes <= 0 ? '+' : '-';
  35. const offsetHours = pad(Math.floor(absOffset / 60));
  36. const offsetMins = pad(absOffset % 60);
  37. const gmtOffset = `GMT${offsetSign}${offsetHours}${offsetMins}`;
  38.  
  39. // Time zone name (e.g., Eastern Daylight Time)
  40. const tzName = date.toString().match(/\(([^)]+)\)/)?.[1] || Intl.DateTimeFormat().resolvedOptions().timeZone;
  41.  
  42. return `${year}-${month}-${day} ${hour}:${minute}:${second}.${milliseconds} (${gmtOffset} [${tzName}])`;
  43. }
  44.  
  45. class Logger {
  46. constructor(name) {
  47. this.information = {
  48. name: name,
  49. timeElapsed: 0,
  50. logs: []
  51. };
  52. this.counterNames = [];
  53. this.counterInfo = {};
  54. this.startTime = Date.now();
  55. }
  56.  
  57. get logs() {
  58. return this.information.logs;
  59. }
  60.  
  61. clear() {
  62. this.startTime = Date.now();
  63. this.information.timeElapsed = 0;
  64. this.counterNames = [];
  65. this.counterInfo = {};
  66. this.information.logs = []
  67. }
  68.  
  69. send() {
  70. this.information.timeElapsed = Date.now() - this.startTime;
  71. console.log(this.information);
  72. }
  73.  
  74. message(type, ...msg) {
  75. this.logs.push({
  76. logType: type,
  77. message: (msg.length == 1 ? msg[0] : msg),
  78. time: new Date(Date.now()).toTimeString()
  79. });
  80. }
  81.  
  82. error(...msg) {
  83. this.message("error", ...msg);
  84. }
  85.  
  86. warn(...msg) {
  87. this.message("warn", ...msg);
  88. }
  89.  
  90. log(...msg) {
  91. this.message("log", ...msg);
  92. }
  93.  
  94. count(label) {
  95. if (!this.counterNames.includes(label)) {
  96. const index = this.logs.length;
  97. this.counterInfo[label] = index;
  98. this.counterNames.push(label);
  99. this.message("counter", {
  100. label: label,
  101. count: 0,
  102. time: {
  103. start: formatDate(new Date(Date.now())),
  104. end: ""
  105. }
  106. });
  107. }
  108. const countInfo = this.logs[this.counterInfo[label]].message;
  109. countInfo.count += 1;
  110. countInfo.time.end = formatDate(new Date(Date.now()));
  111. }
  112. }
  113.  
  114. async function getVal(key, def) {
  115. return await GM.getValue(key, def);
  116. }
  117.  
  118. async function setVal(key, value) {
  119. return await GM.setValue(key, value);
  120. };
  121.  
  122. let subtitles = [];
  123. let selected = undefined;
  124. let currentLabel = "";
  125. let subtitlesOff = undefined;
  126. let americanEnglish = undefined;
  127. let anyEnglish = undefined;
  128.  
  129. (async function () {
  130. 'use strict';
  131.  
  132. // Specials
  133.  
  134. // Inject CSS to hide the scrollbar but still allow scrolling
  135. const style = document.createElement('style');
  136. style.textContent = `
  137. /* Hide scrollbar for all elements */
  138. ::-webkit-scrollbar {
  139. width: 0px;
  140. height: 0px;
  141. }
  142. html, body {
  143. scrollbar-width: none; /* Firefox */
  144. -ms-overflow-style: none; /* IE 10+ */
  145. }
  146. `;
  147. document.documentElement.appendChild(style);
  148.  
  149. // Toggles
  150. var storedSelection = await getVal("selsub", "Subtitles Off");
  151.  
  152. /**
  153. * The function to be called when a key is pressed down.
  154. * @param {KeyboardEvent} e The keyboard event.
  155. * @returns
  156. */
  157. const onKeyDown = async (e) => {
  158. if (e.key.toLowerCase() === 'c') {
  159. selected = subtitles.find(el => el.classList.contains('preferences-panel__option--selected'));
  160.  
  161. currentLabel = (selected ? selected.getAttribute('aria-label') || '' : '');
  162.  
  163. if (currentLabel !== 'Subtitles Off') {
  164. // If it's not already off, turn it off
  165. if (subtitlesOff) subtitlesOff.click();
  166. storedSelection = "Subtitles Off";
  167. } else {
  168. // Otherwise, try American English, or fallback to any English
  169. if (americanEnglish) {
  170. americanEnglish.click();
  171. storedSelection = "American English";
  172. } else if (anyEnglish) {
  173. anyEnglish.click();
  174. storedSelection = anyEnglish.getAttribute('aria-label');
  175. }
  176. }
  177. await setVal("selsub", storedSelection);
  178. }
  179. };
  180.  
  181. const e1c = () => {
  182. if (currentLabel !== storedSelection) {
  183. const storedElm = subtitles.find(el => el.getAttribute('aria-label') === storedSelection);
  184. if (storedElm) {
  185. storedElm.click();
  186. }
  187. }
  188. };
  189.  
  190. const e2c = () => {
  191. document.addEventListener('keydown', onKeyDown);
  192. };
  193.  
  194. function checker(
  195. end1Callback = undefined, end2Callback = undefined,
  196. interval = 1000, timeout = 10000,
  197. logger = new Logger(`${GM.info.script.name}'s Entry Checking`)
  198. ) {
  199. const startTime = Date.now();
  200. const check = setInterval(async () => {
  201. logger.count("Entry Checking Count");
  202. const entries = Array.from(document.querySelectorAll('div.preferences-panel__entry'));
  203. let ending = false;
  204. if (entries.length != 0) {
  205. ending = true;
  206. const subtitlesEntry = entries.find(el => {
  207. const first = el.firstElementChild;
  208. if (first == null || !first.classList.contains("preferences-panel__title")) {
  209. return false;
  210. }
  211. return first.textContent === "Subtitles";
  212. });
  213. //logger.log(entries);
  214. if (subtitlesEntry) {
  215. subtitles = Array.from(subtitlesEntry.getElementsByTagName("ul").item(0).children);
  216. selected = subtitles.find(el => el.classList.contains('preferences-panel__option--selected'));
  217. currentLabel = (selected ? selected.getAttribute('aria-label') || '' : '');
  218. subtitlesOff = subtitles.find(el => el.getAttribute('aria-label') === 'Subtitles Off');
  219. americanEnglish = subtitles.find(el => el.getAttribute('aria-label') === 'American English');
  220. anyEnglish = subtitles.find(el => {
  221. const label = el.getAttribute('aria-label') || '';
  222. return label.includes('English');
  223. });
  224. if (!subtitlesOff) {
  225. ending = false;
  226. logger.warn("'Subtitles Off' wasn't there, checking again.");
  227. } else {
  228. if (end1Callback) {
  229. end1Callback();
  230. }
  231. }
  232. } else {
  233. ending = false;
  234. logger.warn("Subtitles not found, retrying.");
  235. }
  236. } else if (Date.now() - startTime > timeout) {
  237. ending = true;
  238. logger.error(`Entry Elements not found within time limit: ${timeout} milliseconds`);
  239. }
  240. if (ending) {
  241. clearInterval(check);
  242. if (end2Callback) {
  243. end2Callback();
  244. }
  245. logger.send();
  246. }
  247. }, interval);
  248. }
  249. checker(e1c, e2c);
  250.  
  251. let previousUrl = window.location.href;
  252.  
  253. setInterval(function() {
  254. if (window.location.href !== previousUrl) {
  255. console.log("URL changed (polling):", window.location.href);
  256. checker();
  257. previousUrl = window.location.href;
  258. }
  259. }, 100); // Check every 100 milliseconds
  260. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址