您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
自用公用脚本
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/493023/1363403/UnaTools.js
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); }