UnaTools

自用公用脚本

Från och med 2024-04-20. Se den senaste versionen.

Detta skript bör inte installeras direkt. Det är ett bibliotek för andra skript att inkludera med meta-direktivet // @require https://update.greasyfork.org/scripts/493023/1363403/UnaTools.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

const log = console.log.bind(console, "%c[Tools]:", "font-weight:bold;background-color:#00A0D8;color:#fff;padding: 3px 3px;margin: 5px 0px;border-radius:2px;");
const toastStyle = `.link-toast{position:fixed !important;padding:12px 24px;font-size:14px;border-radius:8px;white-space:nowrap;color:#fff;-webkit-transition:-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);transition:transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98),-webkit-transform 0.4s cubic-bezier(0.22,0.58,0.12,0.98);-webkit-animation:link-msg-move-in-top cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;animation:link-msg-move-in-top cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;z-index:10100;text-align:center;height:auto;line-height:auto}.link-toast.mobile{text-align:center;-webkit-box-shadow:none !important;box-shadow:none !important;width:auto;background-color:transparent !important;position:fixed;top:50vh;left:0;right:0}.link-toast.mobile .toast-text{background-color:rgba(0,0,0,0.8);padding:8px;border-radius:8px;display:inline-block;min-width:50px;word-break:break-all;white-space:pre-wrap}.link-toast.fixed{position:fixed}.link-toast.success{background-color:#47d279;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(71,210,121,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(71,210,121,0.2)}.link-toast.caution{background-color:#ffb243;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(255,190,68,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(255,190,68,0.2)}.link-toast.error{background-color:#ff6464;-webkit-box-shadow:0 0.2em 1em 0.1em rgba(255,100,100,0.2);box-shadow:0 0.2em 1em 0.1em rgba(255,100,100,0.2)}.link-toast.info{background-color:#48bbf8;-webkit-box-shadow:0 0.2em 0.1em 0.1em rgba(72,187,248,0.2);box-shadow:0 0.2em 0.1em 0.1em rgba(72,187,248,0.2)}.link-toast.out-fade{-webkit-animation:link-msg-fade-out cubic-bezier(0.22,0.58,0.12,0.98) 0.4s;animation:link-msg-fade-out cubic-bezier(0.22,0.58,0.12,0.98) 0.4s}@-webkit-keyframes link-msg-move-in-top{from{opacity:0;-webkit-transform:translate(0,5em);transform:translate(0,5em)}to{opacity:1;-webkit-transform:translate(0,0);transform:translate(0,0)}}@keyframes link-msg-move-in-top{from{opacity:0;-webkit-transform:translate(0,5em);transform:translate(0,5em)}to{opacity:1;-webkit-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes link-msg-fade-out{from{opacity:1}to{opacity:0}}@keyframes link-msg-fade-out{from{opacity:1}to{opacity:0}}.el-fade-enter,.el-fade-leave{opacity:0;animation-timing-function:linear;animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.el-fade-enter.el-fade-enter-active{animation-name:el-fade-in;animation-play-state:running}.el-fade-leave.el-fade-leave-active{animation-name:el-fade-out;animation-play-state:running;pointer-events:none}.el-fade-enter-active,.el-fade-leave-active{transition:opacity .3s}@keyframes el-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes el-fade-in{0%{opacity:0}to{opacity:1}}@keyframes el-fade-out{0%{opacity:1}to{opacity:0}}@-webkit-keyframes el-fade-out{0%{opacity:1}to{opacity:0}}`;
const aniStyle = `@keyframes aniLeftToRight{0%{transform:translateX(-32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@keyframes aniBottomToTop{0%{transform:translateY(32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniTopToBottom{0%{transform:translateY(-32px);opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniHideToShow{0%{display:none;opacity:0.2}20%{opacity:0.5}30%{opacity:0.8}100%{opacity:1}}@-webkit-keyframes aniShowToHide{0%{display:none;opacity:1}20%{opacity:0.8}30%{opacity:0.5}100%{opacity:0.3}}.aniDelete{transition:all 0.15s cubic-bezier(0.4,0,1,1);opacity:0.1}@keyframes spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}`;

/**
 * ContentType 映射表
 * @type {{download: string, form: string, json: string}}
 */
const ContentType = {
    json: 'application/json;charset=UTF-8', // json数据格式
    form: 'application/x-www-form-urlencoded; charset=UTF-8', // 表单数据格式
    download: 'application/octet-stream' // 二进制文件流格式,用于download
};
/**
 * 消息提示框类型
 * @type {{warn: string, success: string, error: string, info: string}}
 */
const ToastType = {
    success: 'success', error: 'error', info: 'info', warn: 'caution'
};

/**
 * 消息提示弹窗
 * @param message
 * @param type
 * @param wait
 * @param target
 */
function toast(message = '成功', type = ToastType.info, wait = 3000, target = document.body) { // 签到提示
    const toast = document.createElement("div"); // 创建标签
    toast.className = `link-toast ${type}`;
    toast.style.cssText = "top: 70px; right: 25px;";
    toast.innerHTML = `<span class="toast-text">${message}</span>`;
    target.append(toast); // 添加提示到页面上
    setTimeout(() => {
        fadeOutRemove(toast);
    }, wait);
}
/**
 * 解析boolean
 * @param val
 * @returns { boolean }
 */
function parseBoolean(val) {
    if (["yes", "y", "true", "1", "on"].indexOf(String(val).toLowerCase()) !== -1) return true;
    if (["no", "n", "false", "0", "off"].indexOf(String(val).toLowerCase()) !== -1) return false;
    return Boolean(val);
}

/**
 * 淡入显示
 * @param element
 */
function fadeInShow(element) {
    element.classList.remove("el-hidden");
    element.classList.add("el-fade-enter", "el-fade-enter-active");
    const f = () => {
        element.classList.remove("el-fade-enter", "el-fade-enter-active");
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}

/**
 * 淡出隐藏
 * @param element
 */
function fadeOutHide(element) {
    element.classList.add("el-fade-leave", "el-fade-leave-active");
    const f = () => {
        element.classList.remove("el-fade-leave", "el-fade-leave-active");
        element.classList.add("el-hidden");
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}

/**
 * 淡出删除
 * @param element
 */
function fadeOutRemove(element) {
    element.classList.add("el-fade-leave", "el-fade-leave-active");
    const f = () => {
        aniRemove(element, false, 0);
        element.removeEventListener("animationend", f);
        element.removeEventListener("webkitAnimationEnd", f);
    };
    element.addEventListener("animationend", f);
    element.addEventListener("webkitAnimationEnd", f);
}
/**
 * 单选选择器
 * @param selector
 * @param { Document } element
 * @returns { * }
 */
function $one(selector, element = document) {
    return element.querySelector(selector);
}
/**
 * 多选选择器
 * @param selector
 * @param element
 * @returns { NodeListOf<*> }
 */
function $all(selector, element = document) {
    return element.querySelectorAll(selector);
}

/**
 * 安全删除
 * @param { string } selector
 * @param { boolean } withAni
 * @param { number } wait
 * @returns { void }
 */
function safeRemove(selector, withAni = false, wait = 200) {
    safeFunction(() => {
        let removeNodes = document.querySelectorAll(selector);
        for (let i = 0; i < removeNodes.length; i++) {
            aniRemove(removeNodes[i], withAni, wait);
        }
    });
}

/**
 * 动画删除element
 * @param { Element } node
 * @param { boolean } withAni
 * @param { number } wait
 * @returns { void }
 */
function aniRemove(node, withAni = false, wait = 200) {
    if (withAni) {
        node.classList.add('aniDelete');
        setTimeout(() => {
            node.remove();
        }, wait);
    } else {
        node.remove();
    }
}

/**
 * 安全运行方法
 * @param func
 * @param failCb
 * @returns { void }
 */
function safeFunction(func, failCb) {
    try {
        func();
    } catch (e) {
        failCb && failCb(e);
    }
}

/**
 *
 * @param callback 回调函数, 需要返回是否, True: 结束、False: 相当于定时器
 * callback return:
 *     true  = 倒计时
 *     false = 计时器
 * @param { number } period 周期,如: 200ms
 * @param { boolean }  now 立即执行
 * @param { number } count 次数, -1: Infinity
 * @returns { void }
 */
function retryInterval(callback, period = 50, now = false, count = -1) {
    if (now && count-- !== 0) {
        if (callback()) return;
    }
    const inter = setInterval(() => {
        if (count-- === 0) {
            return clearInterval(inter);
        }
        callback() && clearInterval(inter);
    }, period);
}

/**
 * 等待选择器运行
 * @param selector
 * @param callbackFunc
 * @param time
 * @param notClear
 * @returns { void }
 */
function safeWaitFunc(selector, callbackFunc, time = 60, notClear = false) {
    notClear = notClear || false;
    let doClear = !notClear;
    retryInterval(() => {
        if ((typeof (selector) === "string" && document.querySelector(selector) != null)) {
            callbackFunc(document.querySelector(selector));
            if (doClear) return true;
        } else if (typeof (selector) === "function" && (selector() != null || (selector() || []).length > 0)) {
            callbackFunc(selector()[0]);
            if (doClear) return true;
        }
    }, time, true);
}

/**
 * 生成n位数字字母混合字符串
 * @param n
 * @returns { string }
 */
function generateMixed(n) {
    const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    let res = "";
    for (let i = 0; i < n; i++) {
        const id = Math.floor(Math.random() * chars.length);
        res += chars[id];
    }
    return res;
}

/**
 * 睡眠
 * @param milliseconds
 * @returns { Promise<void> }
 */
function sleep(milliseconds) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, milliseconds);
    });
}

/**
 * 是否是数组
 * @param arr
 * @returns { boolean }
 */
function isArray(arr) {
    const toString = Object.prototype.toString;
    const isArray = Array.isArray || function (arg) {
        return toString.call(arg) === '[object Array]';
    };
    return isArray(arr);
}

/**
 * 是否是对象
 * @param val
 * @returns { boolean }
 */
function isObject(val) {
    return typeof val === 'object' && val !== null && Array.isArray(val) === false;
}

/**
 * 过滤空字段
 * @param obj
 * @returns { {} }
 */
function filterNullUndefined(obj) {
    if (!isObject(obj)) {
        return {};
    }
    return Object.entries(obj).reduce((acc, [key, value]) => {
        if (value !== null && value !== undefined && value !== '') {
            acc[key] = value;
        }
        return acc;
    }, {});
}

/**
 * 递归获取
 * @param key
 * @param source
 * @returns { {} | * }
 */
function acquireChain(key = [], source = {}) {
    if (!isArray(key)) {
        return {};
    }
    return key.reduce((res, k) => {
        if (!res) return {};
        return res[k];
    }, source);
}

/**
 * 是否时期已过
 * @param timestamp
 * @param time
 * @returns { boolean }
 */
function isExpired(timestamp, time) {
    const now = Date.now();
    return now - timestamp * 1000 > time;
}

/**
 * 请求
 * @param { string } path
 * @param { string } method
 * @param { {} } data
 * @param { {} } headers
 * @returns { Promise<any> }
 */
function request(path, method = 'GET', data = {}, headers = {}) {

    /**
     * 转化请求参数
     * @param data
     * @returns { string }
     */
    function transform(data = {}) {
        const esc = encodeURIComponent;
        // 转换参数
        return Object.keys(data)
            .map(k => `${esc(k)}=${esc(data[k])}`)
            .join('&');
    }

    method = method.toLocaleUpperCase();
    const config = {
        method, mode: 'cors', // 跨域
        credentials: 'include', // 允许携带cookie
        headers: Object.assign({
            'Content-type': ContentType.form
        }, headers)
    };

    // 如果是get请求
    if (method === 'GET') {
        // 转换拼接get参数
        let param = transform(data);
        if (param) path += `?${param}`;
    } else if (config.headers["Content-type"] === ContentType.form) {
        config.body = transform(data);
    } else {
        // 其他请求 放入body里面
        config.body = JSON.stringify(data);
    }
    return fetch(path, config).then(response => response.json());
}

/**
 * 开关按钮切换
 * @param e
 */
function arcoSwitchChange(e) {
    const $ev = e || window.event;
    const target = $ev.target || $ev.srcElement;
    const btn = target.closest(".arco-switch");
    if (!btn) return;
    if (btn.classList.contains("arco-switch-checked")) {
        btn.classList.remove("arco-switch-checked");
        btn.setAttribute("aria-checked", false);
    } else {
        btn.classList.add("arco-switch-checked");
        btn.setAttribute("aria-checked", true);
    }
    btn.dispatchEvent(new Event('change'));
}

/**
 * 内部检查
 * @param e
 * @param selector
 * @returns { * }
 */
function insideCheck(e, selector) {
    const $ev = e || window.event;
    const target = $ev.target || $ev.srcElement;
    return target.closest(selector);
}