UnaTools

自用公用脚本

目前為 2024-04-20 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/493023/1363403/UnaTools.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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);
}