TetteLib

A library containing several functions i use often in my other scripts

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

  1. // ==UserScript==
  2. // @name TetteLib
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7
  5. // @description A library containing several functions I use often in my other scripts
  6. // @author TetteDev
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11.  
  12. function assert(condition, message, onAssertErrorHappened = undefined) {
  13. if (!condition) {
  14. console.log(message);
  15. simulateNotification("Assert Failed!", message || "No Message Provided", "Error", -1);
  16. if (onAssertErrorHappened !== undefined) onAssertErrorHappened();
  17. debugger;
  18. throw new Error(message || "Assertion failed");
  19. }
  20. }
  21. function simulateNotification(title, message, type = "info", timeout = 2500) {
  22. const toastId = "simpleToast";
  23. var notificationContainer = document.createElement("div");
  24. notificationContainer.id = toastId;
  25. let existingNotification = document.getElementById(toastId);
  26. if (existingNotification) existingNotification.remove();
  27. notificationContainer.title = "Click to dismiss this message";
  28. var innerContainer = document.createElement("div");
  29. const imgSize = 54;
  30. let imgSrc = "";
  31. let backgroundColor = "";
  32. let fontColor = "";
  33. if (type.toLowerCase() === "debug") {
  34. imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678124-wrench-screwdriver-64.png";
  35. backgroundColor = "#eac100";
  36. fontColor = "#323232";
  37. }
  38. else if (type.toLowerCase() === "error") {
  39. imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678069-sign-error-64.png";
  40. backgroundColor = "#ff0000";
  41. fontColor = "#ffffff";
  42. }
  43. else {
  44. imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678110-sign-info-64.png";
  45. backgroundColor = "#0f0f0f";
  46. fontColor = "#ffffff";
  47. }
  48. notificationContainer.style.cssText
  49. = `position: fixed;
  50. bottom: 15px;
  51. right: 15px;
  52. background-color: ${backgroundColor};
  53. color: ${fontColor};
  54. border: 1px solid #ffffff;
  55. max-width: 20%;
  56. padding-left: 50px;
  57. padding-right: 50px;
  58. padding-top:10px;
  59. box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
  60. z-index: 9999;
  61. opacity: 1;
  62. transition: opacity 1s, border-radius 0.5s;
  63. border-radius: 5px;
  64. cursor: pointer;
  65. `
  66. innerContainer.innerHTML =
  67. `<img src='${imgSrc}' style='width:${imgSize}px;height:${imgSize}px;padding-bottom:10px;display:block;margin:auto;'></img>
  68. <p id='title' style='text-align:center;font-weight:bold;font-size:20px;'>${title}</p>
  69. <p id='message' style='text-align:center;padding-bottom:15px;font-size:15px;'>${message}</p>`;
  70. notificationContainer.appendChild(innerContainer);
  71. notificationContainer.onclick = function() { document.body.removeChild(notificationContainer); notificationContainer = null; }
  72. document.body.appendChild(notificationContainer);
  73. if (type.toLowerCase() === "debug") {
  74. console.warn(`[DEBUG] ${title}: ${message}`);
  75. }
  76. else if (type.toLowerCase() === "error") {
  77. console.error(`[ERROR] ${title}: ${message}`);
  78. }
  79. // Set a timer to fade out the notification after 'timeout' milliseconds if (if 'timeout' is not -1 or less)
  80. if (timeout > -1) {
  81. setTimeout(function() {
  82. if (notificationContainer == null) return;
  83. notificationContainer.style.opacity = 0;
  84. setTimeout(function() {
  85. if (notificationContainer == null) return;
  86. document.body.removeChild(notificationContainer);
  87. }, 500); // Remove the notification after the fade-out animation (adjust as needed)
  88. }, (timeout < 1 ? 2500 : timeout)); // Start the fade-out animation after 5 seconds (adjust as needed)
  89. }
  90. }
  91. const toastId = "permission_prompt";
  92. function showToast(message, button1Text = 'Allow', button2Text = 'Block') {
  93. return new Promise((resolve) => {
  94. const clearAllActivePrompts = () => {
  95. Array.from(document.querySelectorAll(`#${toastId}`)).forEach(toast => toast.remove());
  96. };
  97.  
  98. const toast = document.createElement('div');
  99. toast.id = toastId;
  100. toast.innerHTML = `
  101. <div style="
  102. position: fixed;
  103. top: 20px;
  104. right: 20px;
  105. background: white;
  106. padding: 15px;
  107. border-radius: 5px;
  108. box-shadow: 0 2px 10px rgba(0,0,0,0.2);
  109. z-index: 10000;
  110. display: flex;
  111. flex-direction: column;
  112. gap: 10px;
  113. min-width: 200px;
  114. animation: slideIn 0.3s ease;
  115. border: 2px solid red;
  116. font-weight: bold;
  117. ">
  118. <div style="margin-bottom: 10px; color: black;">${message}</div>
  119. <div style="display: flex; gap: 10px; justify-content: flex-end;">
  120. <button id="btn1" style="padding: 5px 10px; color: black; cursor: pointer;">${button1Text}</button>
  121. <button id="btn2" style="padding: 5px 10px; color: black; cursor: pointer;">${button2Text}</button>
  122. </div>
  123. </div>
  124. `;
  125.  
  126. // Add animation style
  127. const style = document.createElement('style');
  128. style.textContent = `
  129. @keyframes slideIn {
  130. from { transform: translateX(100%); opacity: 0; }
  131. to { transform: translateX(0); opacity: 1; }
  132. }
  133. `;
  134. document.head.appendChild(style);
  135.  
  136. // Add to document
  137. document.body.appendChild(toast);
  138.  
  139. // Button handlers
  140. toast.querySelector('#btn1').onclick = () => {
  141. style.remove();
  142. clearAllActivePrompts();
  143. resolve("allow");
  144. };
  145.  
  146. toast.querySelector('#btn2').onclick = () => {
  147. style.remove();
  148. clearAllActivePrompts();
  149. resolve("block");
  150. };
  151. });
  152. }
  153. function waitUntil(predicate, timeoutMs = 5000, checkIntervalMs = 100) {
  154. return new Promise((resolve, reject) => {
  155. if (typeof predicate !== 'function') {
  156. reject(new Error('Predicate must be a function'));
  157. return;
  158. }
  159.  
  160. const startTime = Date.now();
  161. const check = () => {
  162. const result = predicate();
  163. if (result) {
  164. resolve(result);
  165. return;
  166. }
  167. if (timeoutMs > 0 && Date.now() - startTime >= timeoutMs) {
  168. reject(new Error(`Timeout: Predicate did not become true within ${timeoutMs}ms`));
  169. return;
  170. }
  171. setTimeout(check, checkIntervalMs);
  172. };
  173. check();
  174. });
  175. }
  176. function waitForElement(selector) {
  177. return new Promise((resolve, reject) => {
  178. const el = document.querySelector(selector);
  179. if (el) {resolve(el);}
  180. new MutationObserver((mutationRecords, observer) => {
  181. // Query for elements matching the specified selector
  182. Array.from(document.querySelectorAll(selector)).forEach((element) => {
  183. resolve(element);
  184. //Once we have resolved we don't need the observer anymore.
  185. observer.disconnect();
  186. });
  187. })
  188. .observe(document.documentElement, {
  189. childList: true,
  190. subtree: true
  191. });
  192. });
  193. }
  194. function waitForElementWithTimeout(selector, mustBeVisibleToEye = false, timeout = 3000) {
  195. return new Promise((resolve, reject) => {
  196. if (timeout < 0) timeout = 0;
  197. if (!selector) reject("No selector specified");
  198. const el = document.querySelector(selector);
  199. if (el && (mustBeVisibleToEye ? __visible(el) : true)) {
  200. resolve(el);
  201. }
  202. const timeoutMessage = `Timeout: Element with selector '${selector}' not found within ${timeout} ms`;
  203. const timer = setTimeout(() => {
  204. observer.disconnect();
  205. reject(new Error(timeoutMessage));
  206. }, timeout);
  207. const observer = new MutationObserver((mutationRecords, observer) => {
  208. let elements = Array.from(document.querySelectorAll(selector));
  209. if (elements.length > 0 && mustBeVisibleToEye) elements = elements.filter((el) => __visible(el));
  210. //debugger;
  211. if (elements.length > 0) {
  212. clearTimeout(timer);
  213. observer.disconnect();
  214. resolve(elements[0]);
  215. }
  216. });
  217. observer.observe(document.documentElement, {
  218. childList: true,
  219. subtree: true,
  220. });
  221. });
  222. }
  223. function waitForElementWithTimeoutExtended(selector, options = {timeoutMessage: null, onElementFoundValidatorFunc: null, returnAllMatches: false }, timeoutThresholdMs = 3000) {
  224. return new Promise((resolve, reject) => {
  225. if (timeoutThresholdMs < 0) timeoutThresholdMs = 0;
  226. if (!selector) reject("No selector specified");
  227. if (options && typeof options !== 'object') reject("Options parameter must be an object");
  228. if (options.returnAllMatches) {
  229. let els = Array.from(document.querySelectorAll(selector));
  230. if (els.length > 0) {
  231. if (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function') {
  232. els = els.filter((e) => options.onElementFoundValidatorFunc(e));
  233. if (els.length > 0) resolve(els);
  234. }
  235. else resolve(els);
  236. }
  237. }
  238. else {
  239. const el = document.querySelector(selector);
  240. if (el && (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function' ? options.onElementFoundValidatorFunc(el) : true)) {
  241. resolve(el);
  242. }
  243. }
  244. const timeoutMessage = (options.timeoutMessage || `Timeout: Element with selector '${selector}' not found within ${timeoutThresholdMs} ms`);
  245. const timer = setTimeout(() => {
  246. observer.disconnect();
  247. reject(new Error(timeoutMessage));
  248. }, timeoutThresholdMs);
  249. const observer = new MutationObserver((mutationRecords, observer) => {
  250. let elements = Array.from(document.querySelectorAll(selector));
  251. if (elements.length > 0 && (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function')) elements = elements.filter((el) => options.onElementFoundValidatorFunc(el));
  252. if (elements.length > 0) {
  253. clearTimeout(timer);
  254. observer.disconnect();
  255. if (options.returnAllMatches) resolve(elements);
  256. else resolve(elements[0]);
  257. }
  258. });
  259. observer.observe(document.documentElement, {
  260. childList: true,
  261. subtree: true,
  262. });
  263. });
  264. }
  265. function traverseParentsUntil(startElement, predicateUntil, stopAfterNItteratedParents = -1) {
  266. if (!startElement) return null;
  267. if (!predicateUntil || typeof predicateUntil !== "function") return null;
  268. if (!startElement.parentElement) return predicateUntil(startElement) ? startElement : null;
  269. let parentsItterated = 0;
  270. while (startElement.parentElement) {
  271. if (predicateUntil(startElement.parentElement)) return startElement.parentElement;
  272. else startElement = startElement.parentElement;
  273. if (stopAfterNItteratedParents > 0 && stopAfterNItteratedParents === ++parentsItterated) return null;
  274. }
  275. return null;
  276. }
  277. function traverseChildrenUntil(startElement, predicateUntil, stopAfterNItteratedChildren = -1) {
  278. if (!startElement) return null;
  279. if (!predicateUntil || typeof predicateUntil !== "function") return null;
  280. if (!startElement.firstChild) return predicateUntil(startElement) ? startElement : null;
  281. let childrenItterated = 0;
  282. while (startElement.firstChild) {
  283. if (predicateUntil(startElement.firstChild)) return startElement.firstChild;
  284. else startElement = startElement.firstChild;
  285. if (stopAfterNItteratedChildren > 0 && stopAfterNItteratedChildren === ++childrenItterated) return null;
  286. }
  287. return null;
  288. }
  289. function __visible(el) {
  290. return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
  291. }
  292. function removeAllEventListeners(el, preserveChildrenEvents = true) {
  293. if (!preserveChildrenEvents) {
  294. el.parentNode.replaceChild(el.cloneNode(true), el);
  295. }
  296. else {
  297. var newEl = el.cloneNode(false);
  298. while (el.hasChildNodes()) newEl.appendChild(el.firstChild);
  299. el.parentNode.replaceChild(newEl, el);
  300. }
  301. return el;
  302. }
  303. const DoOnceMap = new Map();
  304. const DoOnce = (action) => {
  305. if (typeof action !== 'function') throw new Error("Function 'DoOnce' expects a function for the 'action' argument");
  306. if (!(typeof String.prototype.hashCode === "function")) {
  307. Object.defineProperty(String.prototype, 'hashCode', {
  308. value: function() {
  309. let hash = 0,
  310. i, chr;
  311. if (this.length === 0) return hash;
  312. const len = this.length;
  313. for (i = 0; i < len; i++) {
  314. chr = this.charCodeAt(i);
  315. hash = ((hash << 5) - hash) + chr;
  316. hash |= 0; // Convert to 32bit integer
  317. }
  318. return hash;
  319. },
  320. writable: true,
  321. configurable: true
  322. });
  323. }
  324.  
  325. const stripWhitespaceExceptQuotes = (str) => {
  326. return str.replace(/\s+(?=(?:[^'"`]*[`'"][^'"`]*[`'"])*[^'"`]*$)/g, '');
  327. };
  328. const fnHash = stripWhitespaceExceptQuotes(action.toString()).hashCode();
  329. if (DoOnceMap.has(fnHash)) return;
  330.  
  331. let returnValue = action();
  332. DoOnceMap.set(fnHash, true);
  333. return returnValue;
  334. };