HIDIVE Video Toggles

Currently we only have one toggle, it's for the HIDIVE subtitles being able to be switched between Off and the last selected subtitles (Defaults to English when no last selected exists) with the 'C' key.

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

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

QingJ © 2025

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