NH_web

Common patterns for working with the WEB API.

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/478440/1304193/NH_web.js

  1. // ==UserScript==
  2. // ==UserLibrary==
  3. // @name NH_web
  4. // @description Common patterns for working with the WEB API.
  5. // @version 6
  6. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html
  7. // @homepageURL https://github.com/nexushoratio/userscripts
  8. // @supportURL https://github.com/nexushoratio/userscripts/issues
  9. // @match https://www.example.com/*
  10. // ==/UserLibrary==
  11. // ==/UserScript==
  12.  
  13. window.NexusHoratio ??= {};
  14.  
  15. window.NexusHoratio.web = (function web() {
  16. 'use strict';
  17.  
  18. /** @type {number} - Bumped per release. */
  19. const version = 6;
  20.  
  21. const NH = window.NexusHoratio.base.ensure(
  22. [{name: 'base', minVersion: 36}]
  23. );
  24.  
  25. /**
  26. * Run querySelector to get an element, then click it.
  27. * @param {Element} base - Where to start looking.
  28. * @param {string[]} selectorArray - CSS selectors to use to find an
  29. * element.
  30. * @param {boolean} [matchSelf=false] - If a CSS selector would match base,
  31. * then use it.
  32. * @returns {boolean} - Whether an element could be found.
  33. */
  34. function clickElement(base, selectorArray, matchSelf = false) {
  35. if (base) {
  36. for (const selector of selectorArray) {
  37. let el = null;
  38. if (matchSelf && base.matches(selector)) {
  39. el = base;
  40. } else {
  41. el = base.querySelector(selector);
  42. }
  43. if (el) {
  44. el.click();
  45. return true;
  46. }
  47. }
  48. }
  49. return false;
  50. }
  51.  
  52. /**
  53. * Bring the Browser's focus onto element.
  54. * @param {Element} element - HTML Element to focus on.
  55. */
  56. function focusOnElement(element) {
  57. if (element) {
  58. const magicTabIndex = -1;
  59. const tabIndex = element.getAttribute('tabindex');
  60. element.setAttribute('tabindex', magicTabIndex);
  61. element.focus();
  62. if (tabIndex) {
  63. element.setAttribute('tabindex', tabIndex);
  64. } else {
  65. element.removeAttribute('tabindex');
  66. }
  67. }
  68. }
  69.  
  70. /**
  71. * Post a bunch of information about an HTML element to issues.
  72. * @param {Element} element - Element to get information about.
  73. * @param {string} name - What area this information came from.
  74. */
  75. function postInfoAboutElement(element, name) {
  76. const msg = `An unsupported element from "${name}" discovered:`;
  77. NH.base.issues.post(msg, element.outerHTML);
  78. }
  79.  
  80. /**
  81. * Determines if the element accepts keyboard input.
  82. * @param {Element} element - HTML Element to examine.
  83. * @returns {boolean} - Indicating whether the element accepts keyboard
  84. * input.
  85. */
  86. function isInput(element) {
  87. let tagName = '';
  88. if ('tagName' in element) {
  89. tagName = element.tagName.toLowerCase();
  90. }
  91. // eslint-disable-next-line no-extra-parens
  92. return (element.isContentEditable ||
  93. ['input', 'textarea'].includes(tagName));
  94. }
  95.  
  96. /**
  97. * @typedef {object} Continuation
  98. * @property {boolean} done - Indicate whether the monitor is done
  99. * processing.
  100. * @property {object} [results] - Optional results object.
  101. */
  102.  
  103. /**
  104. * @callback Monitor
  105. * @param {MutationRecord[]} records - Standard mutation records.
  106. * @returns {Continuation} - Indicate whether done monitoring.
  107. */
  108.  
  109. /**
  110. * Simple function that takes no parameters and returns nothing.
  111. * @callback SimpleFunction
  112. */
  113.  
  114. /**
  115. * @typedef {object} OtmotWhat
  116. * @property {string} name - The name for this observer.
  117. * @property {Element} base - Element to observe.
  118. */
  119.  
  120. /**
  121. * @typedef {object} OtmotHow
  122. * @property {object} observeOptions - MutationObserver().observe() options.
  123. * @property {SimpleFunction} [trigger] - Function to call that triggers
  124. * observable results.
  125. * @property {Monitor} monitor - Callback used to process MutationObserver
  126. * records.
  127. * @property {number} [timeout] - Time to wait for completion in
  128. * milliseconds, default of 0 disables.
  129. */
  130.  
  131. /**
  132. * MutationObserver callback for otmot.
  133. *
  134. * @param {MutationRecord[]} records - Standard mutation records.
  135. * @param {MutationObserver} observer - The invoking observer, enhanced with
  136. * extra properties by *otmot()*.
  137. * @returns {boolean} - The *done* value of the monitor function.
  138. */
  139. const otmotMoCallback = (records, observer) => {
  140. const {done, results} = observer.monitor(records);
  141. observer.logger.log('monitor:', done, results);
  142. if (done) {
  143. observer.disconnect();
  144. clearTimeout(observer.timeoutID);
  145. observer.logger.log('resolving');
  146. observer.resolve(results);
  147. }
  148. return done;
  149. };
  150.  
  151. /**
  152. * One time mutation observer with timeout.
  153. * @param {OtmotWhat} what - What to observe.
  154. * @param {OtmotHow} how - How to observe.
  155. * @returns {Promise<Continuation.results>} - Will resolve with the results
  156. * from monitor when done is true.
  157. */
  158. function otmot(what, how) {
  159. const prom = new Promise((resolve, reject) => {
  160. const observer = new MutationObserver(otmotMoCallback);
  161.  
  162. const {
  163. name,
  164. base,
  165. } = what;
  166. const {
  167. observeOptions,
  168. trigger = () => {}, // eslint-disable-line no-empty-function
  169. timeout = 0,
  170. } = how;
  171.  
  172. observer.monitor = how.monitor;
  173. observer.resolve = resolve;
  174. observer.logger = new NH.base.Logger(`otmot ${name}`);
  175. observer.timeoutID = null;
  176.  
  177. /** Standard setTimeout callback. */
  178. const toCallback = () => {
  179. observer.disconnect();
  180. observer.logger.log('one last try');
  181. if (!otmotMoCallback([], observer)) {
  182. observer.logger.log('rejecting after timeout');
  183. reject(new Error(`otmot ${name} timed out`));
  184. }
  185. };
  186.  
  187. if (timeout) {
  188. observer.timeoutID = setTimeout(toCallback, timeout);
  189. }
  190.  
  191. observer.observe(base, observeOptions);
  192. trigger();
  193. observer.logger.log('running');
  194. // Call once at start in case we missed the change.
  195. otmotMoCallback([], observer);
  196. });
  197.  
  198. return prom;
  199. }
  200.  
  201. /**
  202. * @typedef {object} OtrotWhat
  203. * @property {string} name - The name for this observer.
  204. * @property {Element} base - Element to observe.
  205. */
  206.  
  207. /**
  208. * @typedef {object} OtrotHow
  209. * @property {SimpleFunction} [trigger] - Function to call that triggers
  210. * observable events.
  211. * @property {number} timeout - Time to wait for completion in milliseconds.
  212. */
  213.  
  214. /**
  215. * ResizeObserver callback for otrot.
  216. *
  217. * @param {ResizeObserverEntry[]} entries - Standard resize records.
  218. * @param {ResizeObserver} observer - The invoking observer, enhanced with
  219. * extra properties by *otrot()*.
  220. * @returns {boolean} - Whether a resize was observed.
  221. */
  222. const otrotRoCallback = (entries, observer) => {
  223. const {initialHeight, initialWidth} = observer;
  224. const {clientHeight, clientWidth} = observer.base;
  225. observer.logger.log('observed dimensions:', clientWidth, clientHeight);
  226. const resized = clientHeight !== initialHeight ||
  227. clientWidth !== initialWidth;
  228. if (resized) {
  229. observer.disconnect();
  230. clearTimeout(observer.timeoutID);
  231. observer.logger.log('resolving');
  232. observer.resolve(observer.what);
  233. }
  234. return resized;
  235. };
  236.  
  237. /**
  238. * One time resize observer with timeout.
  239. *
  240. * Will resolve automatically upon first resize change.
  241. * @param {OtrotWhat} what - What to observe.
  242. * @param {OtrotHow} how - How to observe.
  243. * @returns {Promise<OtrotWhat>} - Will resolve with the what parameter.
  244. */
  245. function otrot(what, how) {
  246. const prom = new Promise((resolve, reject) => {
  247. const observer = new ResizeObserver(otrotRoCallback);
  248.  
  249. const {
  250. name,
  251. base,
  252. } = what;
  253. const {
  254. trigger = () => {}, // eslint-disable-line no-empty-function
  255. timeout,
  256. } = how;
  257.  
  258. observer.base = base;
  259. observer.initialHeight = base.clientHeight;
  260. observer.initialWidth = base.clientWidth;
  261.  
  262. observer.what = what;
  263. observer.resolve = resolve;
  264. observer.logger = new NH.base.Logger(`otrot ${name}`);
  265. observer.logger.log(
  266. 'initial dimensions:',
  267. observer.initialWidth,
  268. observer.initialHeight
  269. );
  270.  
  271. /** Standard setTimeout callback. */
  272. const toCallback = () => {
  273. observer.disconnect();
  274. observer.logger.log('one last try');
  275. if (!otrotRoCallback([], observer)) {
  276. observer.logger.log('rejecting after timeout');
  277. reject(new Error(`otrot ${name} timed out`));
  278. }
  279. };
  280.  
  281. observer.timeoutID = setTimeout(toCallback, timeout);
  282.  
  283. observer.observe(base);
  284. trigger();
  285. observer.logger.log('running');
  286. // Call once at start in case we missed the change.
  287. otrotRoCallback([], observer);
  288. });
  289.  
  290. return prom;
  291. }
  292.  
  293. /**
  294. * @callback ResizeAction
  295. * @param {ResizeObserverEntry[]} entries - Standard resize entries.
  296. */
  297.  
  298. /**
  299. * @typedef {object} Otrot2How
  300. * @property {SimpleFunction} [trigger] - Function to call that triggers
  301. * observable events.
  302. * @property {ResizeAction} action - Function to call upon each event
  303. * observed and also at the end of duration.
  304. * @property {number} duration - Time to run in milliseconds.
  305. */
  306.  
  307. /**
  308. * ResizeObserver callback for otrot2.
  309. *
  310. * @param {ResizeObserverEntry[]} entries - Standard resize records.
  311. * @param {ResizeObserver} observer - The invoking observer, enhanced with
  312. * extra properties by *otrot()*.
  313. */
  314. const otrot2RoCallback = (entries, observer) => {
  315. observer.logger.log('calling action');
  316. observer.action(entries);
  317. };
  318.  
  319. /**
  320. * One time resize observer with action callback and duration.
  321. *
  322. * Will resolve upon duration expiration. Uses the same what parameter as
  323. * {@link otrot}.
  324. * @param {OtrotWhat} what - What to observe.
  325. * @param {Otrow2How} how - How to observe.
  326. * @returns {Promise<string>} - Will resolve after duration expires.
  327. */
  328. function otrot2(what, how) {
  329. const prom = new Promise((resolve) => {
  330. const observer = new ResizeObserver(otrot2RoCallback);
  331.  
  332. const {
  333. name,
  334. base,
  335. } = what;
  336. const {
  337. trigger = () => {}, // eslint-disable-line no-empty-function
  338. duration,
  339. } = how;
  340.  
  341. observer.logger = new NH.base.Logger(`otrot2 ${name}`);
  342. observer.action = how.action;
  343.  
  344. /** Standard setTimeout callback. */
  345. const toCallback = () => {
  346. observer.disconnect();
  347. observer.logger.log('one last call');
  348. otrot2RoCallback([], observer);
  349. observer.logger.log('resolving');
  350. resolve(`otrot2 ${name} finished`);
  351. };
  352.  
  353. setTimeout(toCallback, duration);
  354.  
  355. observer.observe(base);
  356. trigger();
  357. observer.logger.log('running');
  358. // Call once at start in case we missed the change.
  359. otrot2RoCallback([], observer);
  360. });
  361.  
  362. return prom;
  363. }
  364.  
  365. /**
  366. * Wait for selector to match using querySelector.
  367. * @param {string} selector - CSS selector.
  368. * @param {number} timeout - Time to wait in milliseconds, 0 disables.
  369. * @returns {Promise<Element>} - Matched element.
  370. */
  371. function waitForSelector(selector, timeout) {
  372. const me = 'waitForSelector';
  373. const logger = new NH.base.Logger(me);
  374. logger.entered(me, selector, timeout);
  375.  
  376. /**
  377. * @implements {Monitor}
  378. * @returns {Continuation} - Indicate whether done monitoring.
  379. */
  380. const monitor = () => {
  381. const element = document.querySelector(selector);
  382. if (element) {
  383. logger.log(`match for ${selector}`, element);
  384. return {done: true, results: element};
  385. }
  386. logger.log('Still waiting for', selector);
  387. return {done: false};
  388. };
  389.  
  390. const what = {
  391. name: me,
  392. base: document,
  393. };
  394.  
  395. const how = {
  396. observeOptions: {childList: true, subtree: true},
  397. monitor: monitor,
  398. timeout: timeout,
  399. };
  400.  
  401. logger.leaving(me);
  402. return otmot(what, how);
  403. }
  404.  
  405. return {
  406. version: version,
  407. clickElement: clickElement,
  408. focusOnElement: focusOnElement,
  409. postInfoAboutElement: postInfoAboutElement,
  410. isInput: isInput,
  411. otmot: otmot,
  412. otrot: otrot,
  413. otrot2: otrot2,
  414. waitForSelector: waitForSelector,
  415. };
  416.  
  417. }());

QingJ © 2025

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