🏠 Home 

TetteLib

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

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/478390/1550902/TetteLib.js

// ==UserScript==
// @name         TetteLib
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  A library containing several functions I use often in my other scripts
// @author       TetteDev
// @match        *://*/*
// @grant        none
// ==/UserScript==
function assert(condition, message, onAssertErrorHappened = undefined) {
if (!condition) {
console.log(message);
simulateNotification("Assert Failed!", message || "No Message Provided", "Error", -1);
if (onAssertErrorHappened !== undefined) onAssertErrorHappened();
debugger;
throw new Error(message || "Assertion failed");
}
}
function simulateNotification(title, message, type = "info", timeout = 2500) {
const toastId = "simpleToast";
var notificationContainer = document.createElement("div");
notificationContainer.id = toastId;
let existingNotification = document.getElementById(toastId);
if (existingNotification) existingNotification.remove();
notificationContainer.title = "Click to dismiss this message";
var innerContainer = document.createElement("div");
const imgSize = 54;
let imgSrc = "";
let backgroundColor = "";
let fontColor = "";
if (type.toLowerCase() === "debug") {
imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678124-wrench-screwdriver-64.png";
backgroundColor = "#eac100";
fontColor = "#323232";
}
else if (type.toLowerCase() === "error") {
imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678069-sign-error-64.png";
backgroundColor = "#ff0000";
fontColor = "#ffffff";
}
else {
imgSrc = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678110-sign-info-64.png";
backgroundColor = "#0f0f0f";
fontColor = "#ffffff";
}
notificationContainer.style.cssText
= `position: fixed;
bottom: 15px;
right: 15px;
background-color: ${backgroundColor};
color: ${fontColor};
border: 1px solid #ffffff;
max-width: 20%;
padding-left: 50px;
padding-right: 50px;
padding-top:10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
z-index: 9999;
opacity: 1;
transition: opacity 1s, border-radius 0.5s;
border-radius: 5px;
cursor: pointer;
`
innerContainer.innerHTML =
`<img src='${imgSrc}' style='width:${imgSize}px;height:${imgSize}px;padding-bottom:10px;display:block;margin:auto;'></img>
<p id='title' style='text-align:center;font-weight:bold;font-size:20px;'>${title}</p>
<p id='message' style='text-align:center;padding-bottom:15px;font-size:15px;'>${message}</p>`;
notificationContainer.appendChild(innerContainer);
notificationContainer.onclick = function() { document.body.removeChild(notificationContainer); notificationContainer = null; }
document.body.appendChild(notificationContainer);
if (type.toLowerCase() === "debug") {
console.warn(`[DEBUG] ${title}: ${message}`);
}
else if (type.toLowerCase() === "error") {
console.error(`[ERROR] ${title}: ${message}`);
}
// Set a timer to fade out the notification after 'timeout' milliseconds if (if 'timeout' is not -1 or less)
if (timeout > -1) {
setTimeout(function() {
if (notificationContainer == null) return;
notificationContainer.style.opacity = 0;
setTimeout(function() {
if (notificationContainer == null) return;
document.body.removeChild(notificationContainer);
}, 500); // Remove the notification after the fade-out animation (adjust as needed)
}, (timeout < 1 ? 2500 : timeout)); // Start the fade-out animation after 5 seconds (adjust as needed)
}
}
const toastId = "permission_prompt";
function showToast(message, button1Text = 'Allow', button2Text = 'Block') {
return new Promise((resolve) => {
const clearAllActivePrompts = () => {
Array.from(document.querySelectorAll(`#${toastId}`)).forEach(toast => toast.remove());
};
const toast = document.createElement('div');
toast.id = toastId;
toast.innerHTML = `
<div style="
position: fixed;
top: 20px;
right: 20px;
background: white;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 10000;
display: flex;
flex-direction: column;
gap: 10px;
min-width: 200px;
animation: slideIn 0.3s ease;
border: 2px solid red;
font-weight: bold;
">
<div style="margin-bottom: 10px; color: black;">${message}</div>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button id="btn1" style="padding: 5px 10px; color: black; cursor: pointer;">${button1Text}</button>
<button id="btn2" style="padding: 5px 10px; color: black; cursor: pointer;">${button2Text}</button>
</div>
</div>
`;
// Add animation style
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(style);
// Add to document
document.body.appendChild(toast);
// Button handlers
toast.querySelector('#btn1').onclick = () => {
style.remove();
clearAllActivePrompts();
resolve("allow");
};
toast.querySelector('#btn2').onclick = () => {
style.remove();
clearAllActivePrompts();
resolve("block");
};
});
}
function waitUntil(predicate, timeoutMs = 5000, checkIntervalMs = 100) {
return new Promise((resolve, reject) => {
if (typeof predicate !== 'function') {
reject(new Error('Predicate must be a function'));
return;
}
const startTime = Date.now();
const check = () => {
const r###lt = predicate();
if (r###lt) {
resolve(r###lt);
return;
}
if (timeoutMs > 0 && Date.now() - startTime >= timeoutMs) {
reject(new Error(`Timeout: Predicate did not become true within ${timeoutMs}ms`));
return;
}
setTimeout(check, checkIntervalMs);
};
check();
});
}
function waitForElement(selector) {
return new Promise((resolve, reject) => {
const el = document.querySelector(selector);
if (el) {resolve(el);}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
});
})
.observe(document.documentElement, {
childList: true,
subtree: true
});
});
}
function waitForElementWithTimeout(selector, mustBeVisibleToEye = false, timeout = 3000) {
return new Promise((resolve, reject) => {
if (timeout < 0) timeout = 0;
if (!selector) reject("No selector specified");
const el = document.querySelector(selector);
if (el && (mustBeVisibleToEye ? __visible(el) : true)) {
resolve(el);
}
const timeoutMessage = `Timeout: Element with selector '${selector}' not found within ${timeout} ms`;
const timer = setTimeout(() => {
observer.disconnect();
reject(new Error(timeoutMessage));
}, timeout);
const observer = new MutationObserver((mutationRecords, observer) => {
let elements = Array.from(document.querySelectorAll(selector));
if (elements.length > 0 && mustBeVisibleToEye) elements = elements.filter((el) => __visible(el));
//debugger;
if (elements.length > 0) {
clearTimeout(timer);
observer.disconnect();
resolve(elements[0]);
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
});
}
function waitForElementWithTimeoutExtended(selector, options = {timeoutMessage: null, onElementFoundValidatorFunc: null, returnAllMatches: false }, timeoutThresholdMs = 3000) {
return new Promise((resolve, reject) => {
if (timeoutThresholdMs < 0) timeoutThresholdMs = 0;
if (!selector) reject("No selector specified");
if (options && typeof options !== 'object') reject("Options parameter must be an object");
if (options.returnAllMatches) {
let els = Array.from(document.querySelectorAll(selector));
if (els.length > 0) {
if (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function') {
els = els.filter((e) => options.onElementFoundValidatorFunc(e));
if (els.length > 0) resolve(els);
}
else resolve(els);
}
}
else {
const el = document.querySelector(selector);
if (el && (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function' ? options.onElementFoundValidatorFunc(el) : true)) {
resolve(el);
}
}
const timeoutMessage = (options.timeoutMessage || `Timeout: Element with selector '${selector}' not found within ${timeoutThresholdMs} ms`);
const timer = setTimeout(() => {
observer.disconnect();
reject(new Error(timeoutMessage));
}, timeoutThresholdMs);
const observer = new MutationObserver((mutationRecords, observer) => {
let elements = Array.from(document.querySelectorAll(selector));
if (elements.length > 0 && (options.onElementFoundValidatorFunc && typeof options.onElementFoundValidatorFunc === 'function')) elements = elements.filter((el) => options.onElementFoundValidatorFunc(el));
if (elements.length > 0) {
clearTimeout(timer);
observer.disconnect();
if (options.returnAllMatches) resolve(elements);
else resolve(elements[0]);
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
});
}
function traverseParentsUntil(startElement, predicateUntil, stopAfterNItteratedParents = -1) {
if (!startElement) return null;
if (!predicateUntil || typeof predicateUntil !== "function") return null;
if (!startElement.parentElement) return predicateUntil(startElement) ? startElement : null;
let parentsItterated = 0;
while (startElement.parentElement) {
if (predicateUntil(startElement.parentElement)) return startElement.parentElement;
else startElement = startElement.parentElement;
if (stopAfterNItteratedParents > 0 && stopAfterNItteratedParents === ++parentsItterated) return null;
}
return null;
}
function traverseChildrenUntil(startElement, predicateUntil, stopAfterNItteratedChildren = -1) {
if (!startElement) return null;
if (!predicateUntil || typeof predicateUntil !== "function") return null;
if (!startElement.firstChild) return predicateUntil(startElement) ? startElement : null;
let childrenItterated = 0;
while (startElement.firstChild) {
if (predicateUntil(startElement.firstChild)) return startElement.firstChild;
else startElement = startElement.firstChild;
if (stopAfterNItteratedChildren > 0 && stopAfterNItteratedChildren === ++childrenItterated) return null;
}
return null;
}
function __visible(el) {
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
}
function removeAllEventListeners(el, preserveChildrenEvents = true) {
if (!preserveChildrenEvents) {
el.parentNode.replaceChild(el.cloneNode(true), el);
}
else {
var newEl = el.cloneNode(false);
while (el.hasChildNodes()) newEl.appendChild(el.firstChild);
el.parentNode.replaceChild(newEl, el);
}
return el;
}
const DoOnceMap = new Map();
const DoOnce = (action) => {
if (typeof action !== 'function') throw new Error("Function 'DoOnce' expects a function for the 'action' argument");
if (!(typeof String.prototype.hashCode === "function")) {
Object.defineProperty(String.prototype, 'hashCode', {
value: function() {
let hash = 0,
i, chr;
if (this.length === 0) return hash;
const len = this.length;
for (i = 0; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
},
writable: true,
configurable: true
});
}
const stripWhitespaceExceptQuotes = (str) => {
return str.replace(/\s+(?=(?:[^'"`]*[`'"][^'"`]*[`'"])*[^'"`]*$)/g, '');
};
const fnHash = stripWhitespaceExceptQuotes(action.toString()).hashCode();
if (DoOnceMap.has(fnHash)) return;
let returnValue = action();
DoOnceMap.set(fnHash, true);
return returnValue;
};