This script contains some simple basic-functions.
Fra og med
Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greasyfork.org/scripts/547732/1774573/BasicLib.js
// ==UserScript==
// @name BasicLib
// @description This script contains some simple basic-functions.
// @namespace https://greasyfork.org/users/788550
// @version 1.1.0
// @author Cyrano68
// @license MIT
// @grant none
// @run-at document-start
// ==/UserScript==
// This is a IIFE (Immediately Invoked Function Expression).
(function()
{
"use strict";
// Verify if the object "console" exists and if it has the function "log".
// Then use the "bind" method to ensure that the "log" function is always executed within the context of the "console" object.
const logger = (window.console && console.log) ? console.log.bind(console) : function(){};
let showLogToScreen = false;
let maxNumScreenLogs = 200;
const myVersion = "1.1.0"; // It must be the same value indicated in @version.
consoleLog(`CY==> BasicLib: HELLO! Loading script (version: ${myVersion})...`);
function logToScreen(now, text)
{
const DEBUG_CONTAINER_ID = "DebugContainer";
const LOG_AREA_ID = `${DEBUG_CONTAINER_ID}-logArea`;
let host = document.getElementById("my-proxy-host");
let debugContainer;
let logArea;
if (!host)
{
// 1. Create the host.
host = document.createElement("div");
host.id = "my-proxy-host";
document.body.appendChild(host);
// 2. Create a "shadow DOM tree" and attach it to the "host" element.
let shadow = host.attachShadow({ mode: "open" });
// 3. Create the debug-container.
debugContainer = document.createElement("div");
debugContainer.id = DEBUG_CONTAINER_ID;
debugContainer.style.cssText = `
all: initial;
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
background: rgba(0, 0, 0, 0.95);
color: #00ff00;
font-family: monospace;
font-size: 12px;
z-index: 200000;
border-bottom: 2px solid #444;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
pointer-events: auto;
display: flex;
flex-direction: column;
`;
// Create the LogArea for the messages (it is scrollable).
logArea = document.createElement("div");
logArea.id = LOG_AREA_ID;
logArea.style.cssText = `
max-height: 200px;
overflow-y: auto;
padding: 10px;
flex-grow: 1;
`;
// Create a container for the buttons.
const btnBar = document.createElement("div");
btnBar.style.cssText = `
display: flex;
background: #222;
`;
// Helper-function for creating the buttons.
const createBtn = (label, color, width) =>
{
const btn = document.createElement("button");
btn.innerText = label;
btn.style.cssText = `
${width ? `width: ${width}` : 'flex: 1'};
background: ${color};
color: #fff;
border: none;
padding: 8px;
cursor: pointer;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
border-right: 1px solid #555;
`;
return btn;
};
// This is a "flex" button.
const clearBtn = createBtn("CLEAR LOGS", "#444");
clearBtn.onclick = function()
{
logArea.innerHTML = "";
};
// This is a "fixed-width" button.
const scanBtn = createBtn("SCAN SHADOW", "#0055aa", "100px");
scanBtn.onclick = () =>
{
consoleLog(`CY==> BasicLib: --- STARTING DEEP SHADOW-DOM SCAN ---`);
// Recursive function.
const scanDeep = (root) =>
{
const elements = root.querySelectorAll('*');
elements.forEach(el =>
{
if (el.shadowRoot)
{
const tagName = el.tagName.toUpperCase();
const mode = el.shadowRoot.mode.toUpperCase();
consoleLog(`CY==> BasicLib: ShadowRoot FOUND: TagName='${tagName}', Mode='${mode}'`);
if (mode === "OPEN")
{
const internalElements = el.shadowRoot.querySelectorAll('[id]');
const internalIds = Array.from(internalElements).map(i => i.id).join(', ');
if (internalIds)
{
consoleLog(`CY==> BasicLib: => Internal IDs: ${internalIds}`);
}
else
{
consoleLog(`CY==> BasicLib: => NO Internal ID found`);
}
// Recursive call.
scanDeep(el.shadowRoot);
}
else
{
consoleLog(`CY==> BasicLib: => Content of CLOSED ShadowRoot cannot be inspected`);
}
}
});
};
// Avvia la scansione dal documento principale
scanDeep(document);
consoleLog(`CY==> BasicLib: --- SHADOW-DOM SCAN COMPLETED ---`);
};
// This is a "fixed-width" button.
const closeBtn = createBtn("HIDE", "#600", "100px");
closeBtn.onclick = function()
{
debugContainer.style.display = "none";
};
btnBar.appendChild(clearBtn);
btnBar.appendChild(scanBtn);
btnBar.appendChild(closeBtn);
debugContainer.appendChild(logArea);
debugContainer.appendChild(btnBar);
// 4. Add the DebugContainer to the "Shadow DOM", NOT to the body!
//document.body.appendChild(debugContainer);
shadow.appendChild(debugContainer);
}
else
{
// IMPORTANT: Here we are looking for elements into the "Shadow root", NOT into the document!
let shadow = host.shadowRoot;
debugContainer = shadow.getElementById(DEBUG_CONTAINER_ID);
logArea = shadow.getElementById(LOG_AREA_ID);
}
if (debugContainer.style.display === "none")
{
debugContainer.style.display = "flex";
}
if (text !== undefined)
{
const newEntry = document.createElement("div");
newEntry.style.cssText = `
border-bottom: 1px solid #333;
padding: 4px 0;
white-space: pre-wrap;
word-break: break-all;
`;
// Check if "text" is empty and check for html special characters.
const newEntryText = (text === "" ? " " : text)
.replace(/</g, "<") // Check "<"
.replace(/>/g, ">"); // Check ">"
newEntry.innerHTML = `<span style="color: #888; font-size: 10px;">[${now}]</span> ${newEntryText}`;
// The new entry is prepended to (i.e. inserted to the begin of) the LogArea.
logArea.prepend(newEntry);
if (logArea.childNodes.length > maxNumScreenLogs)
{
for (let i = 0; i < 10; ++i)
{
if (logArea.lastChild)
{
logArea.removeChild(logArea.lastChild);
}
}
}
}
}
function consoleLog(text, showLog = true)
{
if (showLog)
{
const dateNow = new Date();
//const now = dateNow.toISOString();
const now = dateNow.toLocaleString() + "." + dateNow.getMilliseconds().toString().padStart(3, "0");
logger(`${now} ${text}`);
if (showLogToScreen)
{
logToScreen(now, text);
}
}
}
function getMathRandomInteger(val1, val2)
{
let min = 0;
let max = 100;
const val1IsValid = ((val1 !== undefined) && (val1 !== undefined));
const val2IsValid = ((val2 !== undefined) && (val2 !== undefined));
consoleLog(`CY==> BasicLib: getMathRandomInteger - val1IsValid=${val1IsValid}, val2IsValid=${val2IsValid}`);
if (val1IsValid || val2IsValid)
{
if (val1IsValid && val2IsValid)
{
min = val1;
max = val2;
}
else
{
min = 0;
max = (val1IsValid ? val1 : val2);
}
}
if (max < min)
{
const tmp = min;
min = max;
max = tmp;
}
consoleLog(`CY==> BasicLib: getMathRandomInteger - min=${min}, max=${max}`);
return Math.floor(Math.random() * (max - min)) + min;
}
function generateID()
{
// NOTE-1: The function "toString(36)" converts the number to a base-36 string (i.e. a string containing digits 0-9 and letters a-z).
// NOTE-2: THe function "substring(2)" is used to remove the "0." from the start of the random number string.
const ID = Date.now().toString(36) + "_" + Math.random().toString(36).substring(2);
consoleLog(`CY==> BasicLib: generateID - ID='${ID}'`);
return ID;
}
function setInterval2(callback, interval_ms, execCallbackNow)
{
// I defined a new "setInterval" function because I want to call the "setInterval" function and then
// (if required) call immediately the callback function, instead of wait for the first timeout.
//
// Call the "setInterval" for the periodic timer.
consoleLog(`CY==> BasicLib: setInterval2 - STARTING TIMER - interval_ms=${interval_ms}`);
const timerId = setInterval(callback, interval_ms);
consoleLog(`CY==> BasicLib: setInterval2 - TIMER STARTED - timerId=${timerId}`);
if (execCallbackNow)
{
// Call immediately the callback function.
callback(timerId);
}
return timerId;
}
function textMatchesArray(text, array, index)
{
// This function returns true if there is an element in the array that matches with "text".
//
// IMPORTANT: The input "index" must be an object with a field named "value". For example:
// let index = {value: -1};
// At the end the "index.value" will contain the index of the element of the array that matched with "text" (or -1 if there is no match).
//
for (let i = 0; i < array.length; ++i)
{
if (text.startsWith(array[i]))
{
index.value = i;
return true;
}
}
index.value = -1;
return false;
}
function setShowLogToScreen(showLogToScreenIn, maxNumScreenLogsIn)
{
showLogToScreen = showLogToScreenIn;
maxNumScreenLogs = ((maxNumScreenLogsIn !== undefined) && (maxNumScreenLogsIn !== null)) ? maxNumScreenLogsIn : 200;
consoleLog(`CY==> BasicLib: setShowLogToScreen - showLogToScreen=${showLogToScreen}, maxNumScreenLogs=${maxNumScreenLogs}`);
}
function getVersion()
{
return myVersion;
}
// Expose the public interface by returning an object.
window.BasicLib =
{
consoleLog: consoleLog,
getMathRandomInteger: getMathRandomInteger,
generateID: generateID,
setInterval2: setInterval2,
textMatchesArray: textMatchesArray,
setShowLogToScreen: setShowLogToScreen,
getVersion: getVersion
};
consoleLog("CY==> BasicLib: Script loaded");
})();