USToolkit

simple toolkit to help me create userscripts

이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greatest.deepsurf.us/scripts/526417/1604489/USToolkit.js을(를) 사용하여 포함하는 라이브러리입니다.

  1. // ==UserScript==
  2. // @name USToolkit
  3. // @namespace https://greatest.deepsurf.us/pt-BR/users/821661
  4. // @version 0.0.4
  5. // @run-at document-start
  6. // @author hdyzen
  7. // @description simple toolkit to help me create userscripts
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. /**
  12. * Some functions are strongly inspired by:
  13. * github.com/violentmonkey/
  14. * github.com/gorhill/uBlock/
  15. *
  16. */
  17.  
  18. (() => {
  19. function observer(func) {
  20. const observer = new MutationObserver(() => {
  21. const disconnect = func();
  22.  
  23. if (disconnect === true) {
  24. observer.disconnect();
  25. }
  26. });
  27.  
  28. observer.observe(document, { childList: true, subtree: true });
  29. }
  30.  
  31. function waitElement(selector, timeoutSeconds = 10) {
  32. return new Promise((resolve, reject) => {
  33. const element = document.querySelector(selector);
  34. if (element) {
  35. resolve(element);
  36. }
  37.  
  38. const mutationsHandler = () => {
  39. const target = document.querySelector(selector);
  40. if (target) {
  41. observer.disconnect();
  42. resolve(target);
  43. }
  44. };
  45.  
  46. const observer = new MutationObserver(mutationsHandler);
  47.  
  48. observer.observe(document.documentElement || document, {
  49. childList: true,
  50. subtree: true,
  51. });
  52.  
  53. setTimeout(() => {
  54. observer.disconnect();
  55. reject(`Timeout ${timeoutSeconds} seconds!`);
  56. }, timeoutSeconds * 1000);
  57. });
  58. }
  59. const asyncQuerySelector = waitElement;
  60.  
  61. function matchProp(obj, propChain) {
  62. if (!obj || typeof propChain !== "string") {
  63. return;
  64. }
  65.  
  66. const props = propChain.split(".");
  67. let current = obj;
  68.  
  69. for (let i = 0; i < props.length; i++) {
  70. const prop = props[i];
  71.  
  72. if (current === undefined || current === null) {
  73. return;
  74. }
  75.  
  76. if (prop === "[]" && Array.isArray(current)) {
  77. i++;
  78. current = handleArray(current, props[i]);
  79. continue;
  80. }
  81.  
  82. if ((prop === "{}" || prop === "*") && isObject(current)) {
  83. i++;
  84. current = handleObject(current, props[i]);
  85. continue;
  86. }
  87.  
  88. current = current[prop];
  89. }
  90.  
  91. return current;
  92. }
  93.  
  94. function handleArray(arr, nextProp) {
  95. const results = arr.map((item) => getProp(item, nextProp)).filter((val) => val !== undefined);
  96.  
  97. return results.length === 1 ? results[0] : results;
  98. }
  99.  
  100. function handleObject(obj, nextProp) {
  101. const keys = Object.keys(obj);
  102. const results = keys.map((key) => getProp(obj[key], nextProp)).filter((val) => val !== undefined);
  103.  
  104. return results.length === 1 ? results[0] : results;
  105. }
  106.  
  107. function getProp(obj, prop) {
  108. if (obj && Object.hasOwn(obj, prop)) {
  109. return obj[prop];
  110. }
  111.  
  112. return;
  113. }
  114.  
  115. function isObject(val) {
  116. return Object.prototype.toString.call(val) === "[object Object]";
  117. }
  118.  
  119. function getValType(val) {
  120. return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
  121. }
  122.  
  123. function update(func, time = 250) {
  124. const exec = () => {
  125. if (func() === true) {
  126. return;
  127. }
  128.  
  129. setTimeout(() => {
  130. requestAnimationFrame(exec);
  131. }, time);
  132. };
  133.  
  134. requestAnimationFrame(exec);
  135. }
  136.  
  137. function patch(owner, methodName, handler) {
  138. const originalMethod = owner[methodName];
  139.  
  140. if (typeof originalMethod !== "function") {
  141. throw new Error(`The method ${methodName}” was not found in the object "${owner}".`);
  142. }
  143.  
  144. const proxy = new Proxy(originalMethod, handler);
  145.  
  146. owner[methodName] = proxy;
  147.  
  148. return () => {
  149. owner[methodName] = originalMethod;
  150. };
  151. }
  152.  
  153. function onUrlChange(callback) {
  154. let previousUrl = location.href;
  155.  
  156. const observer = new MutationObserver(() => {
  157. if (location.href !== previousUrl) {
  158. previousUrl = location.href;
  159. callback(location.href);
  160. }
  161. });
  162.  
  163. observer.observe(document.body || document.documentElement || document, { childList: true, subtree: true });
  164.  
  165. const historyHandler = {
  166. apply(target, thisArg, args) {
  167. const result = Reflect.apply(target, thisArg, args);
  168. setTimeout(() => {
  169. if (location.href !== previousUrl) {
  170. previousUrl = location.href;
  171. callback(location.href);
  172. }
  173. }, 0);
  174. return result;
  175. },
  176. };
  177.  
  178. patch(history, "pushState", historyHandler);
  179. patch(history, "replaceState", historyHandler);
  180. }
  181.  
  182. function request(options) {
  183. return new Promise((resolve, reject) => {
  184. GM_xmlhttpRequest({
  185. ...options,
  186. onload: resolve,
  187. onerror: reject,
  188. ontimeout: reject,
  189. });
  190. });
  191. }
  192.  
  193. function extractProps(element, propsArray) {
  194. const data = {};
  195.  
  196. for (const propDefinition of propsArray) {
  197. const [label, valuePath] = propDefinition.split(":");
  198.  
  199. if (valuePath) {
  200. data[label] = matchProp(element, valuePath);
  201. } else {
  202. data[label] = matchProp(element, label);
  203. }
  204. }
  205. return data;
  206. }
  207.  
  208. function handleStringRule(container, rule) {
  209. const element = container.querySelector(rule);
  210. return element ? element.textContent.trim() : null;
  211. }
  212.  
  213. function handleArrayRule(container, rule) {
  214. const [subSelector, ...propsToGet] = rule;
  215. const element = !subSelector ? container : container.querySelector(subSelector);
  216. return extractProps(element, propsToGet);
  217. }
  218.  
  219. const ruleHandlers = {
  220. string: handleStringRule,
  221. array: handleArrayRule,
  222. };
  223.  
  224. function getRuleType(rule) {
  225. if (typeof rule === "string") return "string";
  226. if (Array.isArray(rule)) return "array";
  227. return "unknown";
  228. }
  229.  
  230. function processObjectSchema(container, schema) {
  231. const item = {};
  232. for (const key in schema) {
  233. const rule = schema[key];
  234. const ruleType = getRuleType(rule);
  235.  
  236. const handler = ruleHandlers[ruleType];
  237. if (handler) {
  238. item[key] = handler(container, rule);
  239. continue;
  240. }
  241.  
  242. console.warn(`[USToolkit.scrape] Rule for key ${key}” has an unsupported type.`);
  243. }
  244. return item;
  245. }
  246.  
  247. function processContainer(container, schema) {
  248. if (Array.isArray(schema)) {
  249. return extractProps(container, schema);
  250. }
  251.  
  252. if (isObject(schema)) {
  253. return processObjectSchema(container, schema);
  254. }
  255.  
  256. console.warn("[USToolkit.scrape] Invalid schema format.");
  257. return {};
  258. }
  259.  
  260. function scrape(containerSelector, schema, scope = document) {
  261. const containers = scope.querySelectorAll(containerSelector);
  262. const results = [];
  263.  
  264. for (const container of containers) {
  265. const item = processContainer(container, schema);
  266. results.push(item);
  267. }
  268.  
  269. return results;
  270. }
  271.  
  272. window.UST = window.UST || {};
  273.  
  274. Object.assign(window.UST, {
  275. observer,
  276. waitElement,
  277. asyncQuerySelector,
  278. matchProp,
  279. handleArray,
  280. handleObject,
  281. getProp,
  282. isObject,
  283. update,
  284. patch,
  285. onUrlChange,
  286. request,
  287. scrape,
  288. });
  289. })();