mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-10-10 20:06:36 +08:00
add missing file
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
import { __publicField } from './constants.js';
|
||||
import { javDbApi } from '../api/javdb.js';
|
||||
import { GM_openInTab } from 'vite-plugin-monkey/dist/client';
|
||||
|
||||
class Utils {
|
||||
constructor() {
|
||||
__publicField(this, "intervalContainer", {});
|
||||
__publicField(this, "mimeTypes", {
|
||||
txt: "text/plain",
|
||||
html: "text/html",
|
||||
css: "text/css",
|
||||
csv: "text/csv",
|
||||
json: "application/json",
|
||||
xml: "application/xml",
|
||||
jpg: "image/jpeg",
|
||||
jpeg: "image/jpeg",
|
||||
png: "image/png",
|
||||
gif: "image/gif",
|
||||
webp: "image/webp",
|
||||
svg: "image/svg+xml",
|
||||
pdf: "application/pdf",
|
||||
doc: "application/msword",
|
||||
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
xls: "application/vnd.ms-excel",
|
||||
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
ppt: "application/vnd.ms-powerpoint",
|
||||
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
zip: "application/zip",
|
||||
rar: "application/x-rar-compressed",
|
||||
"7z": "application/x-7z-compressed",
|
||||
mp3: "audio/mpeg",
|
||||
wav: "audio/wav",
|
||||
mp4: "video/mp4",
|
||||
webm: "video/webm",
|
||||
ogg: "audio/ogg"
|
||||
});
|
||||
__publicField(this, "timers", new Map);
|
||||
__publicField(this, "insertStyle", (css => {
|
||||
if (css) {
|
||||
-1 === css.indexOf("<style>") && (css = "<style>" + css + "</style>");
|
||||
$("head").append(css);
|
||||
}
|
||||
}));
|
||||
__publicField(this, "layerIndexStack", []);
|
||||
Utils.instance || (Utils.instance = this);
|
||||
return Utils.instance;
|
||||
}
|
||||
importResource(url) {
|
||||
let tag;
|
||||
if (url.indexOf("css") >= 0) {
|
||||
tag = document.createElement("link");
|
||||
tag.setAttribute("rel", "stylesheet");
|
||||
tag.href = url;
|
||||
} else {
|
||||
tag = document.createElement("script");
|
||||
tag.setAttribute("type", "text/javascript");
|
||||
tag.src = url;
|
||||
}
|
||||
document.documentElement.appendChild(tag);
|
||||
}
|
||||
openPage(url, title, shadeClose, event) {
|
||||
if (!url) throw new Error("未传入url");
|
||||
shadeClose = shadeClose ?? !0;
|
||||
if (event && (event.ctrlKey || event.metaKey)) {
|
||||
GM_openInTab(url.includes("http") ? url : window.location.origin + url, {
|
||||
insert: 0
|
||||
});
|
||||
return;
|
||||
}
|
||||
let finalUrl = url;
|
||||
url.includes("/actors/") || url.includes("/star/") || (finalUrl = url.includes("?") ? `${url}&hideNav=1` : `${url}?hideNav=1`);
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: title,
|
||||
content: finalUrl,
|
||||
scrollbar: !1,
|
||||
shadeClose: shadeClose,
|
||||
area: this.getResponsiveArea([ "85%", "90%" ]),
|
||||
isOutAnim: !1,
|
||||
anim: -1,
|
||||
success: (layero, index) => {
|
||||
this.setupEscClose(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
_handleGlobalEscKey(e) {
|
||||
if ("Escape" !== e.key && 27 !== e.keyCode) return;
|
||||
if (0 === this.layerIndexStack.length) return;
|
||||
const topLayerIndex = this.layerIndexStack[this.layerIndexStack.length - 1], $layer = $(`#layui-layer${topLayerIndex}`);
|
||||
let viewerExists = !1;
|
||||
if ($layer.find(".viewer-container").length > 0) viewerExists = !0; else {
|
||||
const iframe = $layer.find(`#layui-layer-iframe${topLayerIndex}`)[0];
|
||||
if (iframe && iframe.contentDocument) try {
|
||||
$(iframe.contentDocument).find(".viewer-container").length > 0 && (viewerExists = !0);
|
||||
} catch (error) {
|
||||
clog.warn("无法检查跨域 iframe 内的 .viewer-container");
|
||||
}
|
||||
}
|
||||
if (!viewerExists) {
|
||||
this.layerIndexStack.pop();
|
||||
layer.close(topLayerIndex);
|
||||
}
|
||||
}
|
||||
setupEscClose(layerIndex) {
|
||||
var _a2;
|
||||
if (!this._boundHandler) {
|
||||
this._boundHandler = this._handleGlobalEscKey.bind(this);
|
||||
$(document).off("keydown.globalLayerEsc");
|
||||
$(document).on("keydown.globalLayerEsc", this._boundHandler);
|
||||
}
|
||||
-1 === this.layerIndexStack.indexOf(layerIndex) && this.layerIndexStack.push(layerIndex);
|
||||
const $iframe = $(`#layui-layer-iframe${layerIndex}`), eventNamespace = `keydown.layerEsc${layerIndex}`;
|
||||
try {
|
||||
const iframeDocument = null == (_a2 = $iframe[0]) ? void 0 : _a2.contentDocument;
|
||||
if (iframeDocument) {
|
||||
if ("yes" === $iframe.attr("data-esc-bound")) return;
|
||||
$(iframeDocument).off(eventNamespace);
|
||||
$(iframeDocument).on(eventNamespace, this._boundHandler);
|
||||
$iframe.attr("data-esc-bound", "yes");
|
||||
}
|
||||
} catch (e) {
|
||||
clog.error("iframe监听失败 (跨域或未加载完毕):", e);
|
||||
}
|
||||
}
|
||||
closePage() {
|
||||
storageManager.getSetting("needClosePage", "yes").then((needClosePage => {
|
||||
if ("yes" !== needClosePage) return;
|
||||
parent.document.documentElement.style.overflow = "auto";
|
||||
[ ".layui-layer-shade", ".layui-layer-move", ".layui-layer" ].forEach((function(selector) {
|
||||
const elements = parent.document.querySelectorAll(selector);
|
||||
if (elements.length > 0) {
|
||||
const elementToRemove = elements.length > 1 ? elements[elements.length - 1] : elements[0];
|
||||
elementToRemove.parentNode.removeChild(elementToRemove);
|
||||
}
|
||||
}));
|
||||
window.close();
|
||||
}));
|
||||
}
|
||||
loopDetector(condition, after, detectInterval = 20, timeout = 1e4, runWhenTimeout = !0) {
|
||||
const uuid = Math.random(), start = (new Date).getTime(), stopAndRun = shouldRun => {
|
||||
clearInterval(this.intervalContainer[uuid]);
|
||||
shouldRun && after && after();
|
||||
delete this.intervalContainer[uuid];
|
||||
};
|
||||
this.intervalContainer[uuid] = setInterval((() => {
|
||||
const timeElapsed = (new Date).getTime() - start;
|
||||
condition() ? stopAndRun(!0) : timeElapsed >= timeout && stopAndRun(runWhenTimeout);
|
||||
}), detectInterval);
|
||||
}
|
||||
rightClick(container, targetSelector, callback) {
|
||||
let containerElement;
|
||||
"string" == typeof container ? containerElement = document.querySelector(container) : container instanceof HTMLElement && (containerElement = container);
|
||||
if (!containerElement) {
|
||||
console.warn("rightClick(), 容器无效或未提供,将使用 document.body 进行全局委托。");
|
||||
containerElement = document.body;
|
||||
}
|
||||
"string" == typeof targetSelector && "" !== targetSelector.trim() ? containerElement.addEventListener("contextmenu", (event => {
|
||||
const targetElement = event.target.closest(targetSelector);
|
||||
targetElement && callback(event, targetElement);
|
||||
})) : console.error("rightClick(), 必须提供有效的 targetSelector。");
|
||||
}
|
||||
q(event, msg, fun, cancelFun) {
|
||||
let x, y;
|
||||
if (event) {
|
||||
x = event.clientX - 130;
|
||||
y = event.clientY - 120;
|
||||
} else {
|
||||
x = window.innerWidth / 2 - 120;
|
||||
y = window.innerHeight / 2 - 120;
|
||||
}
|
||||
let confirmIndex = layer.confirm(msg, {
|
||||
offset: [ y, x ],
|
||||
title: "提示",
|
||||
btn: [ "确定", "取消" ],
|
||||
shade: 0,
|
||||
zIndex: 999999991
|
||||
}, (function() {
|
||||
fun && fun();
|
||||
layer.close(confirmIndex);
|
||||
}), (function() {
|
||||
cancelFun && cancelFun();
|
||||
}));
|
||||
}
|
||||
alert(event, msg, yesFun) {
|
||||
let offset;
|
||||
event && (offset = [ event.clientX - 200, event.clientY - 120 ]);
|
||||
let confirmIndex = layer.alert(msg, {
|
||||
offset: offset,
|
||||
shade: 0,
|
||||
zIndex: 999999991
|
||||
}, (function() {
|
||||
yesFun && yesFun();
|
||||
layer.close(confirmIndex);
|
||||
}));
|
||||
}
|
||||
getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) {
|
||||
let now;
|
||||
now = dateString ? new Date(dateString) : new Date;
|
||||
const year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, "0"), day = String(now.getDate()).padStart(2, "0"), hours = String(now.getHours()).padStart(2, "0"), minutes = String(now.getMinutes()).padStart(2, "0"), seconds = String(now.getSeconds()).padStart(2, "0");
|
||||
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
|
||||
}
|
||||
formatDate(date, dateSplitStr = "-", timeSplitStr = ":") {
|
||||
let targetDate;
|
||||
if (date instanceof Date) targetDate = date; else {
|
||||
if ("string" != typeof date) throw new Error("Invalid date input: must be Date object or date string");
|
||||
targetDate = new Date(date);
|
||||
if (isNaN(targetDate.getTime())) throw new Error("Invalid date string");
|
||||
}
|
||||
const year = targetDate.getFullYear(), month = String(targetDate.getMonth() + 1).padStart(2, "0"), day = String(targetDate.getDate()).padStart(2, "0"), hours = String(targetDate.getHours()).padStart(2, "0"), minutes = String(targetDate.getMinutes()).padStart(2, "0"), seconds = String(targetDate.getSeconds()).padStart(2, "0");
|
||||
return `${[ year, month, day ].join(dateSplitStr)} ${[ hours, minutes, seconds ].join(timeSplitStr)}`;
|
||||
}
|
||||
getHourDifference(date1, date2) {
|
||||
const timestamp1 = date1.getTime(), timestamp2 = date2.getTime(), differenceInHours = Math.abs(timestamp2 - timestamp1) / 36e5;
|
||||
return Math.floor(differenceInHours);
|
||||
}
|
||||
download(data, fileName) {
|
||||
show.info("开始请求下载...");
|
||||
const fileExtension = fileName.split(".").pop().toLowerCase();
|
||||
let blob, mimeType = this.mimeTypes[fileExtension] || "application/octet-stream";
|
||||
if (data instanceof Blob) blob = data; else if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) blob = new Blob([ data ], {
|
||||
type: mimeType
|
||||
}); else if ("string" == typeof data && data.startsWith("data:")) {
|
||||
const byteString = atob(data.split(",")[1]), arrayBuffer = new ArrayBuffer(byteString.length), uintArray = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) uintArray[i] = byteString.charCodeAt(i);
|
||||
blob = new Blob([ uintArray ], {
|
||||
type: mimeType
|
||||
});
|
||||
} else blob = new Blob([ data ], {
|
||||
type: mimeType
|
||||
});
|
||||
const url = URL.createObjectURL(blob), a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout((() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}), 100);
|
||||
}
|
||||
smoothScrollToTop(duration = 500) {
|
||||
return new Promise((resolve => {
|
||||
const start = performance.now(), startPosition = window.pageYOffset;
|
||||
window.requestAnimationFrame((function scrollStep(timestamp) {
|
||||
const elapsed = timestamp - start, progress = Math.min(elapsed / duration, 1), easeInOutCubic = progress < .5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
|
||||
window.scrollTo(0, startPosition * (1 - easeInOutCubic));
|
||||
progress < 1 ? window.requestAnimationFrame(scrollStep) : resolve();
|
||||
}));
|
||||
}));
|
||||
}
|
||||
simpleId() {
|
||||
return crypto.randomUUID().replace("-", "");
|
||||
}
|
||||
isUrl(urlString) {
|
||||
try {
|
||||
new URL(urlString);
|
||||
return !0;
|
||||
} catch (_) {
|
||||
return !1;
|
||||
}
|
||||
}
|
||||
setHrefParam(key, val) {
|
||||
const newUrl = new URL(window.location.href);
|
||||
newUrl.searchParams.set(key, val);
|
||||
window.history.pushState({}, "", newUrl.toString());
|
||||
}
|
||||
getUrlParam(url, key) {
|
||||
const searchString = url.split("?")[1];
|
||||
if (!searchString) return null;
|
||||
const regex = new RegExp(`(?:^|&)${key}=([^&]*)`), match = searchString.match(regex);
|
||||
let value = "";
|
||||
match && match[1] && (value = decodeURIComponent(match[1].replace(/\+/g, " ")));
|
||||
return value ? "true" === value || "false" === value ? "true" === value.toLowerCase() : "string" != typeof value || "" === value.trim() || isNaN(Number(value)) ? value : Number(value) : value;
|
||||
}
|
||||
reBuildSignature() {
|
||||
return javDbApi.buildSignature();
|
||||
}
|
||||
getResponsiveArea(defaultArea) {
|
||||
const screenWidth = window.innerWidth;
|
||||
return screenWidth >= 1200 ? defaultArea || this.getDefaultArea() : screenWidth >= 768 ? [ "70%", "90%" ] : [ "95%", "95%" ];
|
||||
}
|
||||
getDefaultArea() {
|
||||
return [ "85%", "90%" ];
|
||||
}
|
||||
isMobile() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
return [ "iphone", "ipod", "ipad", "android", "blackberry", "windows phone", "nokia", "webos", "opera mini", "mobile", "mobi", "tablet" ].some((keyword => userAgent.includes(keyword)));
|
||||
}
|
||||
copyToClipboard(type, text) {
|
||||
navigator.clipboard.writeText(text).then((() => show.info(`${type}已复制到剪切板, ${text}`))).catch((err => console.error("复制失败: ", err)));
|
||||
}
|
||||
htmlTo$dom(html) {
|
||||
const parser = new DOMParser;
|
||||
return $(parser.parseFromString(html, "text/html"));
|
||||
}
|
||||
addCookie(cookieStr, options = {}) {
|
||||
const {maxAge: maxAge = 604800, path: path = "/", domain: domain = "", secure: secure = !1, sameSite: sameSite = "Lax"} = options;
|
||||
cookieStr.split(";").forEach((cookie => {
|
||||
const trimmed = cookie.trim();
|
||||
if (trimmed) {
|
||||
const parts = trimmed.split("=");
|
||||
if (parts.length >= 2 && parts[0].trim()) {
|
||||
let cookieParts = [ `${parts[0].trim()}=${parts.slice(1).join("=")}` ];
|
||||
maxAge > 0 && cookieParts.push(`max-age=${maxAge}`);
|
||||
cookieParts.push(`path=${path}`);
|
||||
domain && cookieParts.push(`domain=${domain}`);
|
||||
secure && cookieParts.push("Secure");
|
||||
sameSite && cookieParts.push(`SameSite=${sameSite}`);
|
||||
console.log("document.cookie = '" + cookieParts.join("; ") + "'");
|
||||
document.cookie = cookieParts.join("; ");
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
isHidden(el) {
|
||||
const element = el.jquery ? el[0] : el;
|
||||
return !element || (element.offsetWidth <= 0 && element.offsetHeight <= 0 || "none" === window.getComputedStyle(element).display);
|
||||
}
|
||||
time(label = "default", unit = "s", precision = 2) {
|
||||
if (this.timers.has(label)) {
|
||||
const timer = this.timers.get(label), elapsedTime = performance.now() - timer.startTime;
|
||||
let formattedTime, unitLabel;
|
||||
if ("s" === timer.unit) {
|
||||
formattedTime = (elapsedTime / 1e3).toFixed(timer.precision);
|
||||
unitLabel = "秒";
|
||||
} else {
|
||||
formattedTime = elapsedTime.toFixed(timer.precision);
|
||||
unitLabel = "毫秒";
|
||||
}
|
||||
this.timers.delete(label);
|
||||
return `${label}: ${formattedTime}${unitLabel}`;
|
||||
}
|
||||
this.timers.set(label, {
|
||||
startTime: performance.now(),
|
||||
unit: unit,
|
||||
precision: precision
|
||||
});
|
||||
}
|
||||
sleep(ms = 1e3) {
|
||||
return new Promise((resolve => setTimeout(resolve, ms)));
|
||||
}
|
||||
genericSort(arr, sortRules, emptyLast = !0) {
|
||||
if (!Array.isArray(arr) || 0 === arr.length) return [];
|
||||
if (!Array.isArray(sortRules) || 0 === sortRules.length) return [ ...arr ];
|
||||
const sortedArr = [ ...arr ], safeDateConvert = value => {
|
||||
if (value instanceof Date) return value;
|
||||
if ("string" == typeof value) {
|
||||
const date = new Date(value);
|
||||
if (!isNaN(date.getTime())) return date;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
return sortedArr.sort(((a, b) => {
|
||||
for (const rule of sortRules) {
|
||||
const {key: key, order: order = "asc"} = rule;
|
||||
let valA = a, valB = b;
|
||||
if (null != key) if ("function" == typeof key) {
|
||||
valA = key(a);
|
||||
valB = key(b);
|
||||
} else {
|
||||
valA = a && "object" == typeof a ? a[key] : void 0;
|
||||
valB = b && "object" == typeof b ? b[key] : void 0;
|
||||
}
|
||||
const dateA = safeDateConvert(valA), dateB = safeDateConvert(valB);
|
||||
let comparison = 0;
|
||||
const aIsNull = null == valA, bIsNull = null == valB;
|
||||
if (aIsNull && bIsNull) return 0;
|
||||
if (aIsNull) return emptyLast ? 1 : -1;
|
||||
if (bIsNull) return emptyLast ? 1 : -1;
|
||||
comparison = dateA instanceof Date && dateB instanceof Date ? dateA.getTime() - dateB.getTime() : "number" == typeof valA && "number" == typeof valB ? valA - valB : "string" == typeof valA && "string" == typeof valB ? valA.localeCompare(valB) : String(valA).localeCompare(String(valB));
|
||||
"desc" === order && (comparison *= -1);
|
||||
if (0 !== comparison) return comparison;
|
||||
}
|
||||
return 0;
|
||||
}));
|
||||
}
|
||||
async retry(fun, tryCount = 3) {
|
||||
let runCount = 0;
|
||||
for (;runCount < tryCount; ) try {
|
||||
const result = await fun();
|
||||
runCount > 0 && clog.debug(`[重试] 成功,共发起 ${runCount + 1} 次。`);
|
||||
return result;
|
||||
} catch (e) {
|
||||
let errorString = String(e);
|
||||
errorString.startsWith("Error: ") && (errorString = errorString.replace("Error: ", ""));
|
||||
if (errorString.includes("Just a moment") || errorString.includes("重定向") || errorString.toLowerCase().includes("404 page not found") || errorString.toLowerCase().includes("404 not found")) throw e;
|
||||
runCount++;
|
||||
if (runCount === tryCount) {
|
||||
clog.debug(`[重试] 达到最大重试次数 (${tryCount}),最终失败:`, e);
|
||||
throw e;
|
||||
}
|
||||
clog.debug(`[重试] 准备第 ${runCount + 1} 次重试, 错误信息: ${errorString}`);
|
||||
}
|
||||
}
|
||||
copyObj(data) {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
}
|
||||
deepFreeze(obj) {
|
||||
if (null === obj || "object" != typeof obj || Object.isFrozen(obj)) return obj;
|
||||
const propNames = Object.getOwnPropertyNames(obj);
|
||||
for (const name2 of propNames) {
|
||||
const value = obj[name2];
|
||||
value && "object" == typeof value && this.deepFreeze(value);
|
||||
}
|
||||
return Object.freeze(obj);
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncQueue {
|
||||
constructor() {
|
||||
this.queue = Promise.resolve();
|
||||
}
|
||||
addTask(fun) {
|
||||
this.queue = this.queue.then((() => fun())).catch((e => {
|
||||
console.error("执行异步队列任务失败:", e);
|
||||
}));
|
||||
}
|
||||
async waitAllFinished() {
|
||||
return this.queue;
|
||||
}
|
||||
}
|
||||
|
||||
export { Utils, AsyncQueue };
|
||||
Reference in New Issue
Block a user