// ==UserScript==
// @name utils
// @description helper functions, intersection & mutation observers, lazyloader
// @namespace http://tampermonkey.net/
// @author smartacephale
// @license MIT
// @version 1.3.1
// @match *://*/*
// ==/UserScript==
function findNextSibling(el) {
if (el.nextElementSibling) return el.nextElementSibling;
if (el.parentElement) return findNextSibling(el.parentElement);
return null;
}
function parseDOM(html) {
const parsed = new DOMParser().parseFromString(html, 'text/html').body;
return parsed.children.length > 1 ? parsed : parsed.firstElementChild;
}
const MOBILE_UA = [
'Mozilla/5.0 (Linux; Android 10; K)',
'AppleWebKit/537.36 (KHTML, like Gecko)',
'Chrome/114.0.0.0 Mobile Safari/537.36'].join(' ');
function fetchCustomUA(url, ua = MOBILE_UA) {
const headers = new Headers({ "User-Agent": ua });
return fetch(url, { headers });
}
function fetchMobHtml(url) { return fetchCustomUA(url).then((r) => r.text()).then((h) => parseDOM(h)); }
function fetchHtml(url) { return fetch(url).then((r) => r.text()).then((h) => parseDOM(h)); }
function timeToSeconds(t) {
return (t.match(/\d+/gm) || ['0'])
.reverse()
.map((s, i) => parseInt(s) * 60 ** i)
.reduce((a, b) => a + b) || 0;
}
function parseIntegerOr(n, or) {
return Number.isInteger(parseInt(n)) ? parseInt(n) : or;
}
function stringToWords(s) {
return s.split(",").map(s => s.trim().toLowerCase()).filter(_ => _);
}
function parseCSSUrl(s) { return s.replace(/url\("|\"\).*/g, ''); }
function circularShift(n, c = 6, s = 1) { return (n + s) % c || c; }
function range(size, startAt = 1) {
return [...Array(size).keys()].map(i => i + startAt);
}
function listenEvents(dom, events, callback) {
for (const e of events) {
dom.addEventListener(e, callback, true);
}
}
class Observer {
constructor(callback) {
this.callback = callback;
this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
}
observe(target) {
this.observer.observe(target);
}
throttle(target, throttleTime) {
this.observer.unobserve(target);
setTimeout(() => this.observer.observe(target), throttleTime);
}
handleIntersection(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
this.callback(entry.target);
}
}
}
static observeWhile(target, callback, throttleTime) {
const observer_ = new Observer(async (target) => {
const condition = await callback();
if (condition) observer_.throttle(target, throttleTime);
});
observer_.observe(target);
return observer_;
}
}
class LazyImgLoader {
constructor(callback, attributeName = 'data-lazy-load', removeTagAfter = true) {
this.attributeName = attributeName;
this.removeTagAfter = removeTagAfter;
this.lazyImgObserver = new Observer((target) => {
callback(target, this.delazify);
});
}
lazify(target, img, imgSrc) {
if (!img || !imgSrc) return;
img.setAttribute(this.attributeName, imgSrc);
img.src = '';
this.lazyImgObserver.observe(img);
}
delazify = (target) => {
this.lazyImgObserver.observer.unobserve(target);
target.src = target.getAttribute(this.attributeName);
if (this.removeTagAfter) target.removeAttribute(this.attributeName);
}
static create(callback) {
const lazyImgLoader = new LazyImgLoader((target, delazify) => {
if (callback(target)) {
delazify(target);
}
});
return lazyImgLoader;
}
}
function waitForElementExists(parent, selector, callback) {
const observer = new MutationObserver((mutations) => {
const el = parent.querySelector(selector);
if (el) {
observer.disconnect();
callback(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
function watchElementChildrenCount(element, callback) {
let count = element.children.length;
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
if (count !== element.children.length) {
count = element.children.length;
callback(observer, count);
}
}
}
});
observer.observe(element, { childList: true });
}
function watchDomChangesWithThrottle(element, callback, throttle = 1000, options = { childList: true, subtree: true, attributes: true }) {
let lastMutationTime;
let timeout;
const observer = new MutationObserver((mutationList, observer) => {
const now = Date.now();
if (lastMutationTime && now - lastMutationTime < throttle) {
timeout && clearTimeout(timeout);
}
timeout = setTimeout(callback, throttle);
lastMutationTime = now;
});
observer.observe(element, options);
}
class Tick {
constructor(delay, startImmediate = true) {
this.tick = null;
this.delay = delay;
this.startImmediate = startImmediate;
}
start(callback, callbackFinal = null) {
this.stop();
this.callbackFinal = callbackFinal;
if (this.startImmediate) callback();
this.tick = setInterval(callback, this.delay);
}
stop() {
if(this.tick !== null) {
clearInterval(this.tick);
this.tick = null;
}
if (this.callbackFinal) {
this.callbackFinal();
this.callbackFinal = null;
}
}
}
function copyAttributes(target, source) {
for (const attr of source.attributes) {
target.setAttribute(attr.nodeName, attr.nodeValue);
}
}
function replaceElementTag(e, tagName) {
const newTagElement = document.createElement(tagName);
copyAttributes(newTagElement, e);
newTagElement.innerHTML = e.innerHTML;
e.parentNode.replaceChild(newTagElement, e);
}
function getAllUniqueParents(elements) {
return Array.from(elements).reduce((acc, v) => acc.includes(v.parentElement) ? acc : [...acc, v.parentElement], []);
}