面向 115 网盘 Web 端的轻量用户脚本,专注下载、离线任务与界面体验
// ==UserScript==
// @name 115-
// @namespace https://github.com/xndeye/115-minus
// @version 1.0.2
// @author xndeye
// @description 面向 115 网盘 Web 端的轻量用户脚本,专注下载、离线任务与界面体验
// @license MIT
// @icon https://115.com/favicon.ico
// @homepage https://github.com/xndeye/115-minus
// @homepageURL https://github.com/xndeye/115-minus
// @source https://github.com/xndeye/115-minus
// @supportURL https://github.com/xndeye/115-minus/issues
// @match https://115.com/*
// @connect *
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const d$2=new Set;const importCSS = async e=>{d$2.has(e)||(d$2.add(e),(t=>{typeof GM_addStyle=="function"?GM_addStyle(t):(document.head||document.documentElement).appendChild(document.createElement("style")).append(t);})(e));};
const PLAYER_CLASS_NAME = "minus115-video-player";
const PLAYER_PAGE_CLASS_NAME = "minus115-video-player-page";
const MUTED_STORAGE_KEY = "video-muted";
const VOLUME_STORAGE_KEY = "video-volume";
const DEFAULT_MUTED = false;
const DEFAULT_VOLUME = 1;
const readStoredMuted = () => {
const stored = GM_getValue(MUTED_STORAGE_KEY, DEFAULT_MUTED);
return typeof stored === "boolean" ? stored : DEFAULT_MUTED;
};
const readStoredVolume = () => {
const stored = GM_getValue(VOLUME_STORAGE_KEY, DEFAULT_VOLUME);
return typeof stored === "number" && Number.isFinite(stored) && stored >= 0 && stored <= 1 ? stored : DEFAULT_VOLUME;
};
const resolvePlayer = (video) => {
const player = video.parentElement;
if (!player) {
throw new Error("115- 铺满视频播放器失败:video 元素缺少父容器");
}
return player;
};
const setupVideoPlayer = () => {
document.documentElement.classList.add(PLAYER_PAGE_CLASS_NAME);
let boundVideo = null;
let player = null;
let volumeController = null;
let restoreTimer = null;
const bindVideo = (video) => {
volumeController?.abort();
if (restoreTimer !== null) {
window.clearTimeout(restoreTimer);
}
player?.classList.remove(PLAYER_CLASS_NAME);
const storedMuted = readStoredMuted();
const storedVolume = readStoredVolume();
const nextPlayer = resolvePlayer(video);
const nextVolumeController = new AbortController();
video.muted = storedMuted;
video.volume = storedVolume;
nextPlayer.classList.add(PLAYER_CLASS_NAME);
restoreTimer = window.setTimeout(() => {
if (!video.isConnected) {
return;
}
video.muted = storedMuted;
video.volume = storedVolume;
video.addEventListener(
"volumechange",
() => {
GM_setValue(MUTED_STORAGE_KEY, video.muted);
GM_setValue(VOLUME_STORAGE_KEY, video.volume);
},
{ signal: nextVolumeController.signal }
);
restoreTimer = null;
}, 0);
boundVideo = video;
player = nextPlayer;
volumeController = nextVolumeController;
};
const sync = () => {
const video = document.querySelector("video");
if (!(video instanceof HTMLVideoElement) || video === boundVideo) {
return;
}
bindVideo(video);
};
sync();
const observer = new MutationObserver(sync);
observer.observe(document.body, { childList: true, subtree: true });
};
const t$1 = globalThis, e$2 = t$1.ShadowRoot && (void 0 === t$1.ShadyCSS || t$1.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = Symbol(), o$3 = new WeakMap();
let n$2 = class n {
constructor(t2, e2, o2) {
if (this._$cssResult$ = true, o2 !== s$2) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
this.cssText = t2, this.t = e2;
}
get styleSheet() {
let t2 = this.o;
const s2 = this.t;
if (e$2 && void 0 === t2) {
const e2 = void 0 !== s2 && 1 === s2.length;
e2 && (t2 = o$3.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$3.set(s2, t2));
}
return t2;
}
toString() {
return this.cssText;
}
};
const r$2 = (t2) => new n$2("string" == typeof t2 ? t2 : t2 + "", void 0, s$2), i$3 = (t2, ...e2) => {
const o2 = 1 === t2.length ? t2[0] : e2.reduce((e3, s2, o3) => e3 + ((t3) => {
if (true === t3._$cssResult$) return t3.cssText;
if ("number" == typeof t3) return t3;
throw Error("Value passed to 'css' function must be a 'css' function result: " + t3 + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.");
})(s2) + t2[o3 + 1], t2[0]);
return new n$2(o2, t2, s$2);
}, S$1 = (s2, o2) => {
if (e$2) s2.adoptedStyleSheets = o2.map((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet);
else for (const e2 of o2) {
const o3 = document.createElement("style"), n3 = t$1.litNonce;
void 0 !== n3 && o3.setAttribute("nonce", n3), o3.textContent = e2.cssText, s2.appendChild(o3);
}
}, c$2 = e$2 ? (t2) => t2 : (t2) => t2 instanceof CSSStyleSheet ? ((t3) => {
let e2 = "";
for (const s2 of t3.cssRules) e2 += s2.cssText;
return r$2(e2);
})(t2) : t2;
const { is: i$2, defineProperty: e$1, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$1, getOwnPropertySymbols: o$2, getPrototypeOf: n$1 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
switch (s2) {
case Boolean:
t2 = t2 ? l$1 : null;
break;
case Object:
case Array:
t2 = null == t2 ? t2 : JSON.stringify(t2);
}
return t2;
}, fromAttribute(t2, s2) {
let i2 = t2;
switch (s2) {
case Boolean:
i2 = null !== t2;
break;
case Number:
i2 = null === t2 ? null : Number(t2);
break;
case Object:
case Array:
try {
i2 = JSON.parse(t2);
} catch (t3) {
i2 = null;
}
}
return i2;
} }, f$1 = (t2, s2) => !i$2(t2, s2), b$1 = { attribute: true, type: String, converter: u$1, reflect: false, useDefault: false, hasChanged: f$1 };
Symbol.metadata ??= Symbol("metadata"), a$1.litPropertyMetadata ??= new WeakMap();
let y$1 = class y extends HTMLElement {
static addInitializer(t2) {
this._$Ei(), (this.l ??= []).push(t2);
}
static get observedAttributes() {
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
}
static createProperty(t2, s2 = b$1) {
if (s2.state && (s2.attribute = false), this._$Ei(), this.prototype.hasOwnProperty(t2) && ((s2 = Object.create(s2)).wrapped = true), this.elementProperties.set(t2, s2), !s2.noAccessor) {
const i2 = Symbol(), h2 = this.getPropertyDescriptor(t2, i2, s2);
void 0 !== h2 && e$1(this.prototype, t2, h2);
}
}
static getPropertyDescriptor(t2, s2, i2) {
const { get: e2, set: r2 } = h$1(this.prototype, t2) ?? { get() {
return this[s2];
}, set(t3) {
this[s2] = t3;
} };
return { get: e2, set(s3) {
const h2 = e2?.call(this);
r2?.call(this, s3), this.requestUpdate(t2, h2, i2);
}, configurable: true, enumerable: true };
}
static getPropertyOptions(t2) {
return this.elementProperties.get(t2) ?? b$1;
}
static _$Ei() {
if (this.hasOwnProperty(d$1("elementProperties"))) return;
const t2 = n$1(this);
t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), this.elementProperties = new Map(t2.elementProperties);
}
static finalize() {
if (this.hasOwnProperty(d$1("finalized"))) return;
if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
const t3 = this.properties, s2 = [...r$1(t3), ...o$2(t3)];
for (const i2 of s2) this.createProperty(i2, t3[i2]);
}
const t2 = this[Symbol.metadata];
if (null !== t2) {
const s2 = litPropertyMetadata.get(t2);
if (void 0 !== s2) for (const [t3, i2] of s2) this.elementProperties.set(t3, i2);
}
this._$Eh = new Map();
for (const [t3, s2] of this.elementProperties) {
const i2 = this._$Eu(t3, s2);
void 0 !== i2 && this._$Eh.set(i2, t3);
}
this.elementStyles = this.finalizeStyles(this.styles);
}
static finalizeStyles(s2) {
const i2 = [];
if (Array.isArray(s2)) {
const e2 = new Set(s2.flat(1 / 0).reverse());
for (const s3 of e2) i2.unshift(c$2(s3));
} else void 0 !== s2 && i2.push(c$2(s2));
return i2;
}
static _$Eu(t2, s2) {
const i2 = s2.attribute;
return false === i2 ? void 0 : "string" == typeof i2 ? i2 : "string" == typeof t2 ? t2.toLowerCase() : void 0;
}
constructor() {
super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev();
}
_$Ev() {
this._$ES = new Promise((t2) => this.enableUpdating = t2), this._$AL = new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t2) => t2(this));
}
addController(t2) {
(this._$EO ??= new Set()).add(t2), void 0 !== this.renderRoot && this.isConnected && t2.hostConnected?.();
}
removeController(t2) {
this._$EO?.delete(t2);
}
_$E_() {
const t2 = new Map(), s2 = this.constructor.elementProperties;
for (const i2 of s2.keys()) this.hasOwnProperty(i2) && (t2.set(i2, this[i2]), delete this[i2]);
t2.size > 0 && (this._$Ep = t2);
}
createRenderRoot() {
const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
return S$1(t2, this.constructor.elementStyles), t2;
}
connectedCallback() {
this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach((t2) => t2.hostConnected?.());
}
enableUpdating(t2) {
}
disconnectedCallback() {
this._$EO?.forEach((t2) => t2.hostDisconnected?.());
}
attributeChangedCallback(t2, s2, i2) {
this._$AK(t2, i2);
}
_$ET(t2, s2) {
const i2 = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, i2);
if (void 0 !== e2 && true === i2.reflect) {
const h2 = (void 0 !== i2.converter?.toAttribute ? i2.converter : u$1).toAttribute(s2, i2.type);
this._$Em = t2, null == h2 ? this.removeAttribute(e2) : this.setAttribute(e2, h2), this._$Em = null;
}
}
_$AK(t2, s2) {
const i2 = this.constructor, e2 = i2._$Eh.get(t2);
if (void 0 !== e2 && this._$Em !== e2) {
const t3 = i2.getPropertyOptions(e2), h2 = "function" == typeof t3.converter ? { fromAttribute: t3.converter } : void 0 !== t3.converter?.fromAttribute ? t3.converter : u$1;
this._$Em = e2;
const r2 = h2.fromAttribute(s2, t3.type);
this[e2] = r2 ?? this._$Ej?.get(e2) ?? r2, this._$Em = null;
}
}
requestUpdate(t2, s2, i2, e2 = false, h2) {
if (void 0 !== t2) {
const r2 = this.constructor;
if (false === e2 && (h2 = this[t2]), i2 ??= r2.getPropertyOptions(t2), !((i2.hasChanged ?? f$1)(h2, s2) || i2.useDefault && i2.reflect && h2 === this._$Ej?.get(t2) && !this.hasAttribute(r2._$Eu(t2, i2)))) return;
this.C(t2, s2, i2);
}
false === this.isUpdatePending && (this._$ES = this._$EP());
}
C(t2, s2, { useDefault: i2, reflect: e2, wrapped: h2 }, r2) {
i2 && !(this._$Ej ??= new Map()).has(t2) && (this._$Ej.set(t2, r2 ?? s2 ?? this[t2]), true !== h2 || void 0 !== r2) || (this._$AL.has(t2) || (this.hasUpdated || i2 || (s2 = void 0), this._$AL.set(t2, s2)), true === e2 && this._$Em !== t2 && (this._$Eq ??= new Set()).add(t2));
}
async _$EP() {
this.isUpdatePending = true;
try {
await this._$ES;
} catch (t3) {
Promise.reject(t3);
}
const t2 = this.scheduleUpdate();
return null != t2 && await t2, !this.isUpdatePending;
}
scheduleUpdate() {
return this.performUpdate();
}
performUpdate() {
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
for (const [t4, s3] of this._$Ep) this[t4] = s3;
this._$Ep = void 0;
}
const t3 = this.constructor.elementProperties;
if (t3.size > 0) for (const [s3, i2] of t3) {
const { wrapped: t4 } = i2, e2 = this[s3];
true !== t4 || this._$AL.has(s3) || void 0 === e2 || this.C(s3, void 0, i2, e2);
}
}
let t2 = false;
const s2 = this._$AL;
try {
t2 = this.shouldUpdate(s2), t2 ? (this.willUpdate(s2), this._$EO?.forEach((t3) => t3.hostUpdate?.()), this.update(s2)) : this._$EM();
} catch (s3) {
throw t2 = false, this._$EM(), s3;
}
t2 && this._$AE(s2);
}
willUpdate(t2) {
}
_$AE(t2) {
this._$EO?.forEach((t3) => t3.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2);
}
_$EM() {
this._$AL = new Map(), this.isUpdatePending = false;
}
get updateComplete() {
return this.getUpdateComplete();
}
getUpdateComplete() {
return this._$ES;
}
shouldUpdate(t2) {
return true;
}
update(t2) {
this._$Eq &&= this._$Eq.forEach((t3) => this._$ET(t3, this[t3])), this._$EM();
}
updated(t2) {
}
firstUpdated(t2) {
}
};
y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = new Map(), y$1[d$1("finalized")] = new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2");
const t = globalThis, i$1 = (t2) => t2, s$1 = t.trustedTypes, e = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$1 = `lit$${Math.random().toFixed(9).slice(2)}$`, n2 = "?" + o$1, r = `<${n2}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y2 = /^(?:script|style|textarea|title)$/i, x = (t2) => (i2, ...s2) => ({ _$litType$: t2, strings: i2, values: s2 }), b = x(1), E = Symbol.for("lit-noChange"), A = Symbol.for("lit-nothing"), C = new WeakMap(), P = l.createTreeWalker(l, 129);
function V(t2, i2) {
if (!u(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
return void 0 !== e ? e.createHTML(i2) : i2;
}
const N = (t2, i2) => {
const s2 = t2.length - 1, e2 = [];
let n3, l2 = 2 === i2 ? "<svg>" : 3 === i2 ? "<math>" : "", c2 = v;
for (let i3 = 0; i3 < s2; i3++) {
const s3 = t2[i3];
let a2, u2, d2 = -1, f2 = 0;
for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m : void 0 !== u2[2] ? (y2.test(u2[2]) && (n3 = RegExp("</" + u2[2], "g")), c2 = p) : void 0 !== u2[3] && (c2 = p) : c2 === p ? ">" === u2[0] ? (c2 = n3 ?? v, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p : c2 === _ || c2 === m ? c2 = v : (c2 = p, n3 = void 0);
const x2 = c2 === p && t2[i3 + 1].startsWith("/>") ? " " : "";
l2 += c2 === v ? s3 + r : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$1 + x2) : s3 + o$1 + (-2 === d2 ? i3 : x2);
}
return [V(t2, l2 + (t2[s2] || "<?>") + (2 === i2 ? "</svg>" : 3 === i2 ? "</math>" : "")), e2];
};
class S {
constructor({ strings: t2, _$litType$: i2 }, e2) {
let r2;
this.parts = [];
let l2 = 0, a2 = 0;
const u2 = t2.length - 1, d2 = this.parts, [f2, v2] = N(t2, i2);
if (this.el = S.createElement(f2, e2), P.currentNode = this.el.content, 2 === i2 || 3 === i2) {
const t3 = this.el.content.firstChild;
t3.replaceWith(...t3.childNodes);
}
for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) {
if (1 === r2.nodeType) {
if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h)) {
const i3 = v2[a2++], s2 = r2.getAttribute(t3).split(o$1), e3 = /([.?@])?(.*)/.exec(i3);
d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3);
} else t3.startsWith(o$1) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
if (y2.test(r2.tagName)) {
const t3 = r2.textContent.split(o$1), i3 = t3.length - 1;
if (i3 > 0) {
r2.textContent = s$1 ? s$1.emptyScript : "";
for (let s2 = 0; s2 < i3; s2++) r2.append(t3[s2], c()), P.nextNode(), d2.push({ type: 2, index: ++l2 });
r2.append(t3[i3], c());
}
}
} else if (8 === r2.nodeType) if (r2.data === n2) d2.push({ type: 2, index: l2 });
else {
let t3 = -1;
for (; -1 !== (t3 = r2.data.indexOf(o$1, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$1.length - 1;
}
l2++;
}
}
static createElement(t2, i2) {
const s2 = l.createElement("template");
return s2.innerHTML = t2, s2;
}
}
function M(t2, i2, s2 = t2, e2) {
if (i2 === E) return i2;
let h2 = void 0 !== e2 ? s2._$Co?.[e2] : s2._$Cl;
const o2 = a(i2) ? void 0 : i2._$litDirective$;
return h2?.constructor !== o2 && (h2?._$AO?.(false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), void 0 !== e2 ? (s2._$Co ??= [])[e2] = h2 : s2._$Cl = h2), void 0 !== h2 && (i2 = M(t2, h2._$AS(t2, i2.values), h2, e2)), i2;
}
class R {
constructor(t2, i2) {
this._$AV = [], this._$AN = void 0, this._$AD = t2, this._$AM = i2;
}
get parentNode() {
return this._$AM.parentNode;
}
get _$AU() {
return this._$AM._$AU;
}
u(t2) {
const { el: { content: i2 }, parts: s2 } = this._$AD, e2 = (t2?.creationScope ?? l).importNode(i2, true);
P.currentNode = e2;
let h2 = P.nextNode(), o2 = 0, n3 = 0, r2 = s2[0];
for (; void 0 !== r2; ) {
if (o2 === r2.index) {
let i3;
2 === r2.type ? i3 = new k(h2, h2.nextSibling, this, t2) : 1 === r2.type ? i3 = new r2.ctor(h2, r2.name, r2.strings, this, t2) : 6 === r2.type && (i3 = new Z(h2, this, t2)), this._$AV.push(i3), r2 = s2[++n3];
}
o2 !== r2?.index && (h2 = P.nextNode(), o2++);
}
return P.currentNode = l, e2;
}
p(t2) {
let i2 = 0;
for (const s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, i2), i2 += s2.strings.length - 2) : s2._$AI(t2[i2])), i2++;
}
}
class k {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
constructor(t2, i2, s2, e2) {
this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t2, this._$AB = i2, this._$AM = s2, this.options = e2, this._$Cv = e2?.isConnected ?? true;
}
get parentNode() {
let t2 = this._$AA.parentNode;
const i2 = this._$AM;
return void 0 !== i2 && 11 === t2?.nodeType && (t2 = i2.parentNode), t2;
}
get startNode() {
return this._$AA;
}
get endNode() {
return this._$AB;
}
_$AI(t2, i2 = this) {
t2 = M(this, t2, i2), a(t2) ? t2 === A || null == t2 || "" === t2 ? (this._$AH !== A && this._$AR(), this._$AH = A) : t2 !== this._$AH && t2 !== E && this._(t2) : void 0 !== t2._$litType$ ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : d(t2) ? this.k(t2) : this._(t2);
}
O(t2) {
return this._$AA.parentNode.insertBefore(t2, this._$AB);
}
T(t2) {
this._$AH !== t2 && (this._$AR(), this._$AH = this.O(t2));
}
_(t2) {
this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t2 : this.T(l.createTextNode(t2)), this._$AH = t2;
}
$(t2) {
const { values: i2, _$litType$: s2 } = t2, e2 = "number" == typeof s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = S.createElement(V(s2.h, s2.h[0]), this.options)), s2);
if (this._$AH?._$AD === e2) this._$AH.p(i2);
else {
const t3 = new R(e2, this), s3 = t3.u(this.options);
t3.p(i2), this.T(s3), this._$AH = t3;
}
}
_$AC(t2) {
let i2 = C.get(t2.strings);
return void 0 === i2 && C.set(t2.strings, i2 = new S(t2)), i2;
}
k(t2) {
u(this._$AH) || (this._$AH = [], this._$AR());
const i2 = this._$AH;
let s2, e2 = 0;
for (const h2 of t2) e2 === i2.length ? i2.push(s2 = new k(this.O(c()), this.O(c()), this, this.options)) : s2 = i2[e2], s2._$AI(h2), e2++;
e2 < i2.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i2.length = e2);
}
_$AR(t2 = this._$AA.nextSibling, s2) {
for (this._$AP?.(false, true, s2); t2 !== this._$AB; ) {
const s3 = i$1(t2).nextSibling;
i$1(t2).remove(), t2 = s3;
}
}
setConnected(t2) {
void 0 === this._$AM && (this._$Cv = t2, this._$AP?.(t2));
}
}
class H {
get tagName() {
return this.element.tagName;
}
get _$AU() {
return this._$AM._$AU;
}
constructor(t2, i2, s2, e2, h2) {
this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t2, this.name = i2, this._$AM = e2, this.options = h2, s2.length > 2 || "" !== s2[0] || "" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new String()), this.strings = s2) : this._$AH = A;
}
_$AI(t2, i2 = this, s2, e2) {
const h2 = this.strings;
let o2 = false;
if (void 0 === h2) t2 = M(this, t2, i2, 0), o2 = !a(t2) || t2 !== this._$AH && t2 !== E, o2 && (this._$AH = t2);
else {
const e3 = t2;
let n3, r2;
for (t2 = h2[0], n3 = 0; n3 < h2.length - 1; n3++) r2 = M(this, e3[s2 + n3], i2, n3), r2 === E && (r2 = this._$AH[n3]), o2 ||= !a(r2) || r2 !== this._$AH[n3], r2 === A ? t2 = A : t2 !== A && (t2 += (r2 ?? "") + h2[n3 + 1]), this._$AH[n3] = r2;
}
o2 && !e2 && this.j(t2);
}
j(t2) {
t2 === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t2 ?? "");
}
}
class I extends H {
constructor() {
super(...arguments), this.type = 3;
}
j(t2) {
this.element[this.name] = t2 === A ? void 0 : t2;
}
}
class L extends H {
constructor() {
super(...arguments), this.type = 4;
}
j(t2) {
this.element.toggleAttribute(this.name, !!t2 && t2 !== A);
}
}
class z extends H {
constructor(t2, i2, s2, e2, h2) {
super(t2, i2, s2, e2, h2), this.type = 5;
}
_$AI(t2, i2 = this) {
if ((t2 = M(this, t2, i2, 0) ?? A) === E) return;
const s2 = this._$AH, e2 = t2 === A && s2 !== A || t2.capture !== s2.capture || t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== A && (s2 === A || e2);
e2 && this.element.removeEventListener(this.name, this, s2), h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2;
}
handleEvent(t2) {
"function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t2) : this._$AH.handleEvent(t2);
}
}
class Z {
constructor(t2, i2, s2) {
this.element = t2, this.type = 6, this._$AN = void 0, this._$AM = i2, this.options = s2;
}
get _$AU() {
return this._$AM._$AU;
}
_$AI(t2) {
M(this, t2);
}
}
const B = t.litHtmlPolyfillSupport;
B?.(S, k), (t.litHtmlVersions ??= []).push("3.3.3");
const D = (t2, i2, s2) => {
const e2 = s2?.renderBefore ?? i2;
let h2 = e2._$litPart$;
if (void 0 === h2) {
const t3 = s2?.renderBefore ?? null;
e2._$litPart$ = h2 = new k(i2.insertBefore(c(), t3), t3, void 0, s2 ?? {});
}
return h2._$AI(t2), h2;
};
const s = globalThis;
class i extends y$1 {
constructor() {
super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
}
createRenderRoot() {
const t2 = super.createRenderRoot();
return this.renderOptions.renderBefore ??= t2.firstChild, t2;
}
update(t2) {
const r2 = this.render();
this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t2), this._$Do = D(r2, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(true);
}
disconnectedCallback() {
super.disconnectedCallback(), this._$Do?.setConnected(false);
}
render() {
return E;
}
}
i._$litElement$ = true, i["finalized"] = true, s.litElementHydrateSupport?.({ LitElement: i });
const o = s.litElementPolyfillSupport;
o?.({ LitElement: i });
(s.litElementVersions ??= []).push("4.2.2");
class ApplicationError extends Error {
constructor(operation, message, cause) {
super(`${operation}:${message}`);
this.name = "ApplicationError";
this.originalCause = cause;
this.operation = operation;
}
}
class HttpError extends ApplicationError {
constructor(operation, status, responseBody) {
super(operation, `请求失败,状态码 ${status},响应内容:${responseBody || "<空>"}`);
this.name = "HttpError";
this.status = status;
this.responseBody = responseBody;
}
}
const errorMessage = (error) => {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string" && error) {
return error;
}
return String(error);
};
const MAX_ATTEMPTS = 3;
const REQUEST_TIMEOUT_MS = 15e3;
const RETRY_DELAY_MS = 300;
const send$1 = (request) => new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: request.method,
url: request.url,
data: request.data,
headers: request.headers,
timeout: REQUEST_TIMEOUT_MS,
onload: (response) => {
resolve({
status: response.status,
body: response.responseText
});
},
onerror: (error) => {
reject(new ApplicationError(request.operation, `网络请求失败:${String(error.error)}`));
},
ontimeout: () => {
reject(new ApplicationError(request.operation, "网络请求超时"));
}
});
});
const assertSuccess = (request, response) => {
if (response.status < 200 || response.status >= 300) {
throw new HttpError(request.operation, response.status, response.body);
}
return response;
};
const isRetryable = (error) => !(error instanceof HttpError) || error.status === 429 || error.status >= 500;
const waitBeforeRetry = (attempt) => new Promise((resolve) => {
window.setTimeout(resolve, RETRY_DELAY_MS * 2 ** (attempt - 1));
});
const requestTextWithRetry = async (request) => {
let lastError;
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
try {
return assertSuccess(request, await send$1(request));
} catch (error) {
lastError = error;
if (attempt >= MAX_ATTEMPTS || !isRetryable(error)) {
throw error;
}
console.warn("115- 请求重试", {
operation: request.operation,
url: request.url,
attempt,
error
});
await waitBeforeRetry(attempt);
}
}
throw lastError;
};
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
const parseObject = (text, operation) => {
let value;
try {
value = JSON.parse(text);
} catch (error) {
throw new ApplicationError(operation, `响应不是有效 JSON:${text || "<空>"}`, error);
}
if (!isObject(value)) {
throw new ApplicationError(operation, "响应 JSON 不是对象");
}
return value;
};
const requireString = (object, key, operation) => {
const value = object[key];
if (typeof value !== "string" || !value) {
throw new ApplicationError(operation, `响应字段 ${key} 不是非空字符串`);
}
return value;
};
const requireNumber = (object, key, operation) => {
const value = object[key];
const number = typeof value === "string" && value !== "" ? Number(value) : value;
if (typeof number !== "number" || !Number.isFinite(number)) {
throw new ApplicationError(operation, `响应字段 ${key} 不是有效数字`);
}
return number;
};
const optionalString = (object, key) => {
const value = object[key];
return typeof value === "string" ? value : "";
};
const requireObject = (object, key, operation) => {
const value = object[key];
if (!isObject(value)) {
throw new ApplicationError(operation, `响应字段 ${key} 不是对象`);
}
return value;
};
const crypto = (typeof globalThis !== "undefined" ? globalThis : void 0)?.crypto || (typeof global !== "undefined" ? global : void 0)?.crypto || (typeof window !== "undefined" ? window : void 0)?.crypto || (typeof self !== "undefined" ? self : void 0)?.crypto || (typeof frames !== "undefined" ? frames : void 0)?.[0]?.crypto;
let randomWordArray;
if (crypto) randomWordArray = (nBytes) => {
const words = [];
for (let i2 = 0; i2 < nBytes; i2 += 4) words.push(crypto.getRandomValues(new Uint32Array(1))[0]);
return new WordArray(words, nBytes);
};
else randomWordArray = (nBytes) => {
const words = [];
const r2 = (m_w) => {
let _m_w = m_w;
let _m_z = 987654321;
const mask = 4294967295;
return () => {
_m_z = 36969 * (_m_z & 65535) + (_m_z >> 16) & mask;
_m_w = 18e3 * (_m_w & 65535) + (_m_w >> 16) & mask;
let result = (_m_z << 16) + _m_w & mask;
result /= 4294967296;
result += 0.5;
return result * (Math.random() > 0.5 ? 1 : -1);
};
};
let rcache;
for (let i2 = 0; i2 < nBytes; i2 += 4) {
const _r = r2((rcache || Math.random()) * 4294967296);
rcache = _r() * 987654071;
words.push(_r() * 4294967296 | 0);
}
return new WordArray(words, nBytes);
};
var Base = class {
static create(...args) {
return new this(...args);
}
mixIn(properties) {
return Object.assign(this, properties);
}
clone() {
const clone = new this.constructor();
Object.assign(clone, this);
return clone;
}
};
var WordArray = class extends Base {
words;
sigBytes;
constructor(words = [], sigBytes) {
super();
if (words instanceof ArrayBuffer) {
const typedArray = new Uint8Array(words);
this._initFromUint8Array(typedArray);
return;
}
if (ArrayBuffer.isView(words)) {
let uint8Array;
if (words instanceof Uint8Array) uint8Array = words;
else uint8Array = new Uint8Array(words.buffer, words.byteOffset, words.byteLength);
this._initFromUint8Array(uint8Array);
return;
}
this.words = words;
this.sigBytes = sigBytes !== void 0 ? sigBytes : this.words.length * 4;
}
_initFromUint8Array(typedArray) {
const typedArrayByteLength = typedArray.byteLength;
const words = [];
for (let i2 = 0; i2 < typedArrayByteLength; i2 += 1) words[i2 >>> 2] |= typedArray[i2] << 24 - i2 % 4 * 8;
this.words = words;
this.sigBytes = typedArrayByteLength;
}
static random = randomWordArray;
toString(encoder = Hex) {
return encoder.stringify(this);
}
concat(wordArray) {
const thisWords = this.words;
const thatWords = wordArray.words;
const thisSigBytes = this.sigBytes;
const thatSigBytes = wordArray.sigBytes;
this.clamp();
if (thisSigBytes % 4) for (let i2 = 0; i2 < thatSigBytes; i2 += 1) {
const thatByte = thatWords[i2 >>> 2] >>> 24 - i2 % 4 * 8 & 255;
thisWords[thisSigBytes + i2 >>> 2] |= thatByte << 24 - (thisSigBytes + i2) % 4 * 8;
}
else for (let i2 = 0; i2 < thatSigBytes; i2 += 4) thisWords[thisSigBytes + i2 >>> 2] = thatWords[i2 >>> 2];
this.sigBytes += thatSigBytes;
return this;
}
clamp() {
const { words, sigBytes } = this;
words[sigBytes >>> 2] &= 4294967295 << 32 - sigBytes % 4 * 8;
words.length = Math.ceil(sigBytes / 4);
}
clone() {
const clone = super.clone();
clone.words = this.words.slice(0);
return clone;
}
};
const Hex = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const hexChars = [];
for (let i2 = 0; i2 < sigBytes; i2 += 1) {
const bite = words[i2 >>> 2] >>> 24 - i2 % 4 * 8 & 255;
hexChars.push((bite >>> 4).toString(16));
hexChars.push((bite & 15).toString(16));
}
return hexChars.join("");
},
parse(hexStr) {
const hexStrLength = hexStr.length;
const words = [];
for (let i2 = 0; i2 < hexStrLength; i2 += 2) words[i2 >>> 3] |= parseInt(hexStr.substr(i2, 2), 16) << 24 - i2 % 8 * 4;
return new WordArray(words, hexStrLength / 2);
}
};
const Latin1 = {
stringify(wordArray) {
const { words, sigBytes } = wordArray;
const latin1Chars = [];
for (let i2 = 0; i2 < sigBytes; i2 += 1) {
const bite = words[i2 >>> 2] >>> 24 - i2 % 4 * 8 & 255;
latin1Chars.push(String.fromCharCode(bite));
}
return latin1Chars.join("");
},
parse(latin1Str) {
const latin1StrLength = latin1Str.length;
const words = [];
for (let i2 = 0; i2 < latin1StrLength; i2 += 1) words[i2 >>> 2] |= (latin1Str.charCodeAt(i2) & 255) << 24 - i2 % 4 * 8;
return new WordArray(words, latin1StrLength);
}
};
const Utf8 = {
stringify(wordArray) {
try {
return decodeURIComponent(escape(Latin1.stringify(wordArray)));
} catch (e2) {
throw new Error("Malformed UTF-8 data");
}
},
parse(utf8Str) {
return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
}
};
var BufferedBlockAlgorithm = class extends Base {
_minBufferSize = 0;
_data;
_nDataBytes;
constructor() {
super();
}
reset() {
this._data = new WordArray();
this._nDataBytes = 0;
}
_append(data) {
let m_data;
if (typeof data === "string") m_data = Utf8.parse(data);
else m_data = data;
this._data.concat(m_data);
this._nDataBytes += m_data.sigBytes;
}
_process(doFlush) {
let processedWords;
const data = this._data;
const dataWords = data.words;
const dataSigBytes = data.sigBytes;
const blockSizeBytes = this.blockSize * 4;
let nBlocksReady = dataSigBytes / blockSizeBytes;
if (doFlush) nBlocksReady = Math.ceil(nBlocksReady);
else nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
const nWordsReady = nBlocksReady * this.blockSize;
const nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
if (nWordsReady) {
for (let offset = 0; offset < nWordsReady; offset += this.blockSize) this._doProcessBlock(dataWords, offset);
processedWords = dataWords.splice(0, nWordsReady);
data.sigBytes -= nBytesReady;
}
return new WordArray(processedWords || [], nBytesReady);
}
clone() {
const clone = super.clone();
clone._data = this._data.clone();
return clone;
}
};
var Hasher = class extends BufferedBlockAlgorithm {
blockSize = 512 / 32;
cfg;
_hash;
constructor(cfg) {
super();
this.cfg = Object.assign({}, cfg);
this.reset();
}
static _createHelper(SubHasher) {
return (message, cfg) => {
return new SubHasher(cfg).finalize(message);
};
}
static _createHmacHelper(SubHasher) {
return (message, key) => {
return new HMAC(SubHasher, key).finalize(message);
};
}
reset() {
super.reset();
this._doReset();
}
update(messageUpdate) {
this._append(messageUpdate);
this._process();
return this;
}
finalize(messageUpdate) {
if (messageUpdate) this._append(messageUpdate);
const hash = this._doFinalize();
return hash;
}
};
var Hasher32 = class extends Hasher {
};
var HMAC = class HMAC2 extends Base {
_hasher;
_oKey;
_iKey;
constructor(SubHasher, key) {
super();
const hasher = new SubHasher();
this._hasher = hasher;
let _key;
if (typeof key === "string") _key = Utf8.parse(key);
else _key = key;
const hasherBlockSize = hasher.blockSize;
const hasherBlockSizeBytes = hasherBlockSize * 4;
if (_key.sigBytes > hasherBlockSizeBytes) _key = hasher.finalize(_key);
_key.clamp();
const oKey = _key.clone();
this._oKey = oKey;
const iKey = _key.clone();
this._iKey = iKey;
const oKeyWords = oKey.words;
const iKeyWords = iKey.words;
for (let i2 = 0; i2 < hasherBlockSize; i2 += 1) {
oKeyWords[i2] ^= 1549556828;
iKeyWords[i2] ^= 909522486;
}
oKey.sigBytes = hasherBlockSizeBytes;
iKey.sigBytes = hasherBlockSizeBytes;
this.reset();
}
static create(...args) {
const [SubHasher, key] = args;
return new HMAC2(SubHasher, key);
}
reset() {
const hasher = this._hasher;
hasher.reset();
hasher.update(this._iKey);
}
update(messageUpdate) {
this._hasher.update(messageUpdate);
return this;
}
finalize(messageUpdate) {
const hasher = this._hasher;
const innerHash = hasher.finalize(messageUpdate);
hasher.reset();
const hmac = hasher.finalize(this._oKey.clone().concat(innerHash));
return hmac;
}
};
const T = (() => {
const a2 = [];
for (let i2 = 0; i2 < 64; i2 += 1) a2[i2] = Math.abs(Math.sin(i2 + 1)) * 4294967296 | 0;
return a2;
})();
const FF = (a2, b2, c2, d2, x2, s2, t2) => {
const n3 = a2 + (b2 & c2 | ~b2 & d2) + x2 + t2;
return (n3 << s2 | n3 >>> 32 - s2) + b2;
};
const GG = (a2, b2, c2, d2, x2, s2, t2) => {
const n3 = a2 + (b2 & d2 | c2 & ~d2) + x2 + t2;
return (n3 << s2 | n3 >>> 32 - s2) + b2;
};
const HH = (a2, b2, c2, d2, x2, s2, t2) => {
const n3 = a2 + (b2 ^ c2 ^ d2) + x2 + t2;
return (n3 << s2 | n3 >>> 32 - s2) + b2;
};
const II = (a2, b2, c2, d2, x2, s2, t2) => {
const n3 = a2 + (c2 ^ (b2 | ~d2)) + x2 + t2;
return (n3 << s2 | n3 >>> 32 - s2) + b2;
};
var MD5Algo = class extends Hasher32 {
_doReset() {
this._hash = new WordArray([
1732584193,
4023233417,
2562383102,
271733878
]);
}
_doProcessBlock(M2, offset) {
const _M = M2;
for (let i2 = 0; i2 < 16; i2 += 1) {
const offset_i = offset + i2;
const M_offset_i = M2[offset_i];
_M[offset_i] = (M_offset_i << 8 | M_offset_i >>> 24) & 16711935 | (M_offset_i << 24 | M_offset_i >>> 8) & 4278255360;
}
const H2 = this._hash.words;
const M_offset_0 = _M[offset + 0];
const M_offset_1 = _M[offset + 1];
const M_offset_2 = _M[offset + 2];
const M_offset_3 = _M[offset + 3];
const M_offset_4 = _M[offset + 4];
const M_offset_5 = _M[offset + 5];
const M_offset_6 = _M[offset + 6];
const M_offset_7 = _M[offset + 7];
const M_offset_8 = _M[offset + 8];
const M_offset_9 = _M[offset + 9];
const M_offset_10 = _M[offset + 10];
const M_offset_11 = _M[offset + 11];
const M_offset_12 = _M[offset + 12];
const M_offset_13 = _M[offset + 13];
const M_offset_14 = _M[offset + 14];
const M_offset_15 = _M[offset + 15];
let a2 = H2[0];
let b2 = H2[1];
let c2 = H2[2];
let d2 = H2[3];
a2 = FF(a2, b2, c2, d2, M_offset_0, 7, T[0]);
d2 = FF(d2, a2, b2, c2, M_offset_1, 12, T[1]);
c2 = FF(c2, d2, a2, b2, M_offset_2, 17, T[2]);
b2 = FF(b2, c2, d2, a2, M_offset_3, 22, T[3]);
a2 = FF(a2, b2, c2, d2, M_offset_4, 7, T[4]);
d2 = FF(d2, a2, b2, c2, M_offset_5, 12, T[5]);
c2 = FF(c2, d2, a2, b2, M_offset_6, 17, T[6]);
b2 = FF(b2, c2, d2, a2, M_offset_7, 22, T[7]);
a2 = FF(a2, b2, c2, d2, M_offset_8, 7, T[8]);
d2 = FF(d2, a2, b2, c2, M_offset_9, 12, T[9]);
c2 = FF(c2, d2, a2, b2, M_offset_10, 17, T[10]);
b2 = FF(b2, c2, d2, a2, M_offset_11, 22, T[11]);
a2 = FF(a2, b2, c2, d2, M_offset_12, 7, T[12]);
d2 = FF(d2, a2, b2, c2, M_offset_13, 12, T[13]);
c2 = FF(c2, d2, a2, b2, M_offset_14, 17, T[14]);
b2 = FF(b2, c2, d2, a2, M_offset_15, 22, T[15]);
a2 = GG(a2, b2, c2, d2, M_offset_1, 5, T[16]);
d2 = GG(d2, a2, b2, c2, M_offset_6, 9, T[17]);
c2 = GG(c2, d2, a2, b2, M_offset_11, 14, T[18]);
b2 = GG(b2, c2, d2, a2, M_offset_0, 20, T[19]);
a2 = GG(a2, b2, c2, d2, M_offset_5, 5, T[20]);
d2 = GG(d2, a2, b2, c2, M_offset_10, 9, T[21]);
c2 = GG(c2, d2, a2, b2, M_offset_15, 14, T[22]);
b2 = GG(b2, c2, d2, a2, M_offset_4, 20, T[23]);
a2 = GG(a2, b2, c2, d2, M_offset_9, 5, T[24]);
d2 = GG(d2, a2, b2, c2, M_offset_14, 9, T[25]);
c2 = GG(c2, d2, a2, b2, M_offset_3, 14, T[26]);
b2 = GG(b2, c2, d2, a2, M_offset_8, 20, T[27]);
a2 = GG(a2, b2, c2, d2, M_offset_13, 5, T[28]);
d2 = GG(d2, a2, b2, c2, M_offset_2, 9, T[29]);
c2 = GG(c2, d2, a2, b2, M_offset_7, 14, T[30]);
b2 = GG(b2, c2, d2, a2, M_offset_12, 20, T[31]);
a2 = HH(a2, b2, c2, d2, M_offset_5, 4, T[32]);
d2 = HH(d2, a2, b2, c2, M_offset_8, 11, T[33]);
c2 = HH(c2, d2, a2, b2, M_offset_11, 16, T[34]);
b2 = HH(b2, c2, d2, a2, M_offset_14, 23, T[35]);
a2 = HH(a2, b2, c2, d2, M_offset_1, 4, T[36]);
d2 = HH(d2, a2, b2, c2, M_offset_4, 11, T[37]);
c2 = HH(c2, d2, a2, b2, M_offset_7, 16, T[38]);
b2 = HH(b2, c2, d2, a2, M_offset_10, 23, T[39]);
a2 = HH(a2, b2, c2, d2, M_offset_13, 4, T[40]);
d2 = HH(d2, a2, b2, c2, M_offset_0, 11, T[41]);
c2 = HH(c2, d2, a2, b2, M_offset_3, 16, T[42]);
b2 = HH(b2, c2, d2, a2, M_offset_6, 23, T[43]);
a2 = HH(a2, b2, c2, d2, M_offset_9, 4, T[44]);
d2 = HH(d2, a2, b2, c2, M_offset_12, 11, T[45]);
c2 = HH(c2, d2, a2, b2, M_offset_15, 16, T[46]);
b2 = HH(b2, c2, d2, a2, M_offset_2, 23, T[47]);
a2 = II(a2, b2, c2, d2, M_offset_0, 6, T[48]);
d2 = II(d2, a2, b2, c2, M_offset_7, 10, T[49]);
c2 = II(c2, d2, a2, b2, M_offset_14, 15, T[50]);
b2 = II(b2, c2, d2, a2, M_offset_5, 21, T[51]);
a2 = II(a2, b2, c2, d2, M_offset_12, 6, T[52]);
d2 = II(d2, a2, b2, c2, M_offset_3, 10, T[53]);
c2 = II(c2, d2, a2, b2, M_offset_10, 15, T[54]);
b2 = II(b2, c2, d2, a2, M_offset_1, 21, T[55]);
a2 = II(a2, b2, c2, d2, M_offset_8, 6, T[56]);
d2 = II(d2, a2, b2, c2, M_offset_15, 10, T[57]);
c2 = II(c2, d2, a2, b2, M_offset_6, 15, T[58]);
b2 = II(b2, c2, d2, a2, M_offset_13, 21, T[59]);
a2 = II(a2, b2, c2, d2, M_offset_4, 6, T[60]);
d2 = II(d2, a2, b2, c2, M_offset_11, 10, T[61]);
c2 = II(c2, d2, a2, b2, M_offset_2, 15, T[62]);
b2 = II(b2, c2, d2, a2, M_offset_9, 21, T[63]);
H2[0] = H2[0] + a2 | 0;
H2[1] = H2[1] + b2 | 0;
H2[2] = H2[2] + c2 | 0;
H2[3] = H2[3] + d2 | 0;
}
_doFinalize() {
const data = this._data;
const dataWords = data.words;
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = data.sigBytes * 8;
dataWords[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
const nBitsTotalH = Math.floor(nBitsTotal / 4294967296);
const nBitsTotalL = nBitsTotal;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 15] = (nBitsTotalH << 8 | nBitsTotalH >>> 24) & 16711935 | (nBitsTotalH << 24 | nBitsTotalH >>> 8) & 4278255360;
dataWords[(nBitsLeft + 64 >>> 9 << 4) + 14] = (nBitsTotalL << 8 | nBitsTotalL >>> 24) & 16711935 | (nBitsTotalL << 24 | nBitsTotalL >>> 8) & 4278255360;
data.sigBytes = (dataWords.length + 1) * 4;
this._process();
const hash = this._hash;
const H2 = hash.words;
for (let i2 = 0; i2 < 4; i2 += 1) {
const H_i = H2[i2];
H2[i2] = (H_i << 8 | H_i >>> 24) & 16711935 | (H_i << 24 | H_i >>> 8) & 4278255360;
}
return hash;
}
clone() {
const clone = super.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
};
const MD5 = Hasher._createHelper(MD5Algo);
const KEY_TABLE = [
240,
229,
105,
174,
191,
220,
191,
138,
26,
69,
232,
190,
125,
166,
115,
184,
222,
143,
231,
196,
69,
218,
134,
196,
155,
100,
139,
20,
106,
180,
241,
170,
56,
1,
53,
158,
38,
105,
44,
134,
0,
107,
79,
165,
54,
52,
98,
166,
42,
150,
104,
24,
242,
74,
253,
189,
107,
151,
143,
77,
143,
137,
19,
183,
108,
142,
147,
237,
14,
13,
72,
62,
215,
47,
136,
216,
254,
254,
126,
134,
80,
149,
79,
209,
235,
131,
38,
52,
219,
102,
123,
156,
126,
157,
122,
129,
50,
234,
182,
51,
222,
58,
169,
89,
52,
102,
59,
170,
186,
129,
96,
72,
185,
213,
129,
156,
248,
108,
132,
119,
255,
84,
120,
38,
95,
190,
232,
30,
54,
159,
52,
128,
92,
69,
44,
155,
118,
213,
27,
143,
204,
195,
184,
245
];
const SHORT_KEY = [41, 35, 33, 94];
const LONG_KEY = [120, 6, 173, 76, 51, 134, 93, 24, 76, 1, 63, 70];
const RSA_MODULUS = BigInt(
"0x8686980c0f5a24c4b9d43020cd2c22703ff3f450756529058b1cf88f09b8602136477198a6e2683149659bd122c33592fdb5ad47944ad1ea4d36c6b172aad6338c3bb6ac6227502d010993ac967d1aef00f0c8e038de2e4d3bc2ec368af2e9f10a6f1eda4f7262f136420c07c331b871bf139f74f3010e3c4fe57df3afb71683"
);
const RSA_EXPONENT = 0x10001n;
const UTF8_DECODER = new TextDecoder();
const stringToBytes = (value) => Array.from(value, (character) => character.charCodeAt(0));
const bytesToString = (bytes) => String.fromCharCode(...bytes);
const xor = (source, key) => {
const offset = source.length % 4;
return source.map((value, index) => {
const keyIndex = index < offset ? index : index - offset;
const keyValue = key[keyIndex % key.length];
if (keyValue === void 0) {
throw new ApplicationError("生成下载地址", "115 下载编码密钥为空");
}
return value ^ keyValue;
});
};
const deriveKey = (length, key) => {
if (key === null) {
return [...length === 12 ? LONG_KEY : SHORT_KEY];
}
return Array.from({ length }, (_2, index) => {
const value = key[index];
const left = KEY_TABLE[length * index];
const right = KEY_TABLE[length * (length - 1 - index)];
if (value === void 0 || left === void 0 || right === void 0) {
throw new ApplicationError("生成下载地址", "115 下载编码密钥长度无效");
}
return value + left & 255 ^ right;
});
};
const symmetricEncode = (source, key) => {
const first = xor(source, deriveKey(4, key));
return xor(first.reverse(), deriveKey(12, null));
};
const symmetricDecode = (source, key, salt) => {
const first = xor(source, deriveKey(12, salt));
return xor(first.reverse(), deriveKey(4, key));
};
const bytesToHex = (bytes) => bytes.map((value) => value.toString(16).padStart(2, "0")).join("");
const hexToString = (hex) => {
const bytes = hex.match(/.{2}/g);
if (!bytes) {
return "";
}
return bytesToString(bytes.map((value) => Number.parseInt(value, 16)));
};
const modularPower = (baseValue, exponentValue, modulus) => {
let base = baseValue % modulus;
let exponent = exponentValue;
let result = 1n;
while (exponent > 0n) {
if (exponent % 2n === 1n) {
result = result * base % modulus;
}
exponent >>= 1n;
base = base * base % modulus;
}
return result;
};
const pad = (text) => {
const size = 128;
if (text.length + 11 > size) {
throw new ApplicationError("生成下载地址", "115 下载编码块长度超限");
}
const bytes = new Array(size).fill(255);
bytes[0] = 0;
bytes[1] = 2;
const separator = size - text.length - 1;
bytes[separator] = 0;
for (let index = 0; index < text.length; index += 1) {
bytes[separator + 1 + index] = text.charCodeAt(index);
}
return BigInt(`0x${bytesToHex(bytes)}`);
};
const unpad = (value) => {
const hex = value.toString(16).padStart(256, "0");
const decoded = hexToString(hex);
const separator = decoded.indexOf("\0", 2);
if (separator < 0) {
throw new ApplicationError("解析下载地址", "115 下载响应缺少 RSA 分隔符");
}
return decoded.slice(separator + 1);
};
const rsaEncode = (source) => {
const blockSize = 117;
let encrypted = "";
for (let offset = 0; offset < source.length; offset += blockSize) {
const block = bytesToString(source.slice(offset, offset + blockSize));
encrypted += modularPower(pad(block), RSA_EXPONENT, RSA_MODULUS).toString(16).padStart(256, "0");
}
return window.btoa(hexToString(encrypted));
};
const rsaDecode = (source) => {
const blockSize = 128;
let decoded = "";
for (let offset = 0; offset < source.length; offset += blockSize) {
const block = source.slice(offset, offset + blockSize);
decoded += unpad(modularPower(BigInt(`0x${bytesToHex(block)}`), RSA_EXPONENT, RSA_MODULUS));
}
return stringToBytes(decoded);
};
const encodeDownloadRequest = (pickCode, timestamp) => {
const key = stringToBytes(MD5(`!@###@#${timestamp}DFDR@#@#`).toString());
const payload = stringToBytes(JSON.stringify({ pickcode: pickCode }));
const encoded = key.slice(0, 16).concat(symmetricEncode(payload, key));
return {
data: rsaEncode(encoded),
key
};
};
const decodeDownloadResponse = (source, key) => {
const decoded = rsaDecode(stringToBytes(window.atob(source)));
const payload = symmetricDecode(decoded.slice(16), key, decoded.slice(0, 16));
return UTF8_DECODER.decode(Uint8Array.from(payload));
};
const FOLDER_PAGE_SIZE = 1e3;
const readFileEntry = (value) => {
const operation = "读取文件夹内容";
if (!isObject(value)) {
throw new ApplicationError(operation, "文件条目不是对象");
}
const name = requireString(value, "n", operation);
const pickCode = requireString(value, "pc", operation);
const fileId = optionalString(value, "fid");
const directoryId = optionalString(value, "cid");
if (!fileId && !directoryId) {
throw new ApplicationError(operation, `文件条目“${name}”缺少文件或目录 ID`);
}
return {
directoryId,
isDirectory: !fileId,
name,
pickCode,
size: fileId ? requireNumber(value, "s", operation) : 0
};
};
const listFolderPage = async (directoryId, offset) => {
const operation = "读取文件夹内容";
const response = await requestTextWithRetry({
operation,
method: "GET",
url: `https://webapi.115.com/files?aid=1&cid=${encodeURIComponent(directoryId)}&show_dir=1&offset=${offset}&limit=${FOLDER_PAGE_SIZE}&format=json`
});
const json = parseObject(response.body, operation);
if (json.state !== true) {
throw new ApplicationError(operation, optionalString(json, "error") || "115 返回失败状态");
}
if (!Array.isArray(json.data)) {
throw new ApplicationError(operation, "响应字段 data 不是数组");
}
return {
entries: json.data.map(readFileEntry),
total: requireNumber(json, "count", operation)
};
};
const listFolder = async (directoryId) => {
const operation = "读取文件夹内容";
const entries = [];
const pickCodes = new Set();
let offset = 0;
while (true) {
const page = await listFolderPage(directoryId, offset);
for (const entry of page.entries) {
if (!pickCodes.has(entry.pickCode)) {
pickCodes.add(entry.pickCode);
entries.push(entry);
}
}
if (entries.length >= page.total) {
return entries;
}
if (page.entries.length === 0) {
throw new ApplicationError(
operation,
`目录 ${directoryId} 分页提前结束,预期 ${page.total} 项,实际读取 ${entries.length} 项`
);
}
offset += page.entries.length;
}
};
const getDownloadLink = async (pickCode) => {
const operation = "获取文件下载地址";
const timestamp = Math.floor(Date.now() / 1e3);
const encoded = encodeDownloadRequest(pickCode, timestamp);
const response = await requestTextWithRetry({
operation,
method: "POST",
url: `http://proapi.115.com/app/chrome/downurl?t=${timestamp}`,
data: `data=${encodeURIComponent(encoded.data)}`,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
const json = parseObject(response.body, operation);
if (json.state !== true) {
throw new ApplicationError(
operation,
optionalString(json, "msg") || optionalString(json, "error") || "115 返回失败状态"
);
}
const encrypted = requireString(json, "data", operation);
const decoded = parseObject(decodeDownloadResponse(encrypted, encoded.key), operation);
const first = Object.values(decoded)[0];
if (!isObject(first)) {
throw new ApplicationError(operation, "解密响应中没有文件信息");
}
const urlObject = requireObject(first, "url", operation);
return {
name: requireString(first, "file_name", operation),
pickCode: requireString(first, "pick_code", operation),
url: requireString(urlObject, "url", operation)
};
};
const isFiber = (value) => typeof value === "object" && value !== null && "return" in value;
const readPageFile = (checkbox) => {
const key = Object.keys(checkbox).find((item) => item.startsWith("__reactFiber$"));
const value = key ? Reflect.get(checkbox, key) : void 0;
let fiber = isFiber(value) ? value : void 0;
while (fiber && !fiber.memoizedProps?.file) {
fiber = fiber.return ?? void 0;
}
if (!fiber?.memoizedProps?.file) {
throw new ApplicationError("读取选中文件", "115 页面组件结构已变化,无法获取文件信息");
}
return fiber.memoizedProps.file;
};
const normalizePageFile = (file) => {
const operation = "读取选中文件";
const name = file.n ?? file.name;
const pickCode = file.pc;
const isDirectory = file.type === "folder" || file.file_type === "folder" || !file.fid;
if (!name || !pickCode) {
throw new ApplicationError(operation, "文件名称或提取码为空");
}
if (isDirectory && !file.cid) {
throw new ApplicationError(operation, `文件夹“${name}”缺少目录 ID`);
}
const rawSize = file.s ?? file.size;
const size = typeof rawSize === "string" && rawSize !== "" ? Number(rawSize) : rawSize;
let fileSize = 0;
if (!isDirectory && (typeof size !== "number" || !Number.isFinite(size) || size < 0)) {
throw new ApplicationError(operation, `文件“${name}”缺少有效大小`);
}
if (typeof size === "number") {
fileSize = size;
}
return {
directoryId: isDirectory ? file.cid ?? "" : "",
isDirectory,
name,
pickCode,
size: fileSize
};
};
const getSelectedFiles = () => Array.from(
document.querySelectorAll(
'.file-list-wrap .checkbox-area input[type="checkbox"]:checked'
),
(checkbox) => normalizePageFile(readPageFile(checkbox))
);
const getSelectedFileCount = () => document.querySelectorAll('.file-list-wrap .checkbox-area input[type="checkbox"]:checked').length;
const featureStyles = i$3`
:host {
color: var(--minus115-text);
font-family: var(--minus115-font);
}
button,
input,
textarea {
box-sizing: border-box;
font: inherit;
}
button {
min-height: 32px;
padding: 5px 14px;
border: 1px solid var(--minus115-border);
border-radius: 6px;
color: var(--minus115-text);
background: var(--minus115-surface);
cursor: pointer;
}
button:hover:not(:disabled) {
border-color: var(--minus115-primary);
color: var(--minus115-primary);
}
button.primary {
border-color: var(--minus115-primary);
color: #fff;
background: var(--minus115-primary);
}
button.danger {
color: var(--minus115-danger);
}
button.link {
min-height: auto;
padding: 2px 4px;
border: 0;
color: var(--minus115-primary);
background: transparent;
}
button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
input[type='text'],
input[type='password'],
textarea {
width: 100%;
border: 1px solid var(--minus115-border);
border-radius: 6px;
color: var(--minus115-text);
background: var(--minus115-surface);
outline: none;
}
input[type='text'],
input[type='password'] {
height: 34px;
padding: 0 10px;
}
textarea {
min-height: 210px;
padding: 10px;
resize: vertical;
}
input:focus,
textarea:focus {
border-color: var(--minus115-primary);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--minus115-primary) 18%, transparent);
}
.actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
.empty,
.muted {
color: var(--minus115-muted);
}
.loading {
display: flex;
min-height: 100px;
align-items: center;
justify-content: center;
color: var(--minus115-muted);
}
`;
const notify = (target, kind, message) => {
target.dispatchEvent(
new CustomEvent("minus115-notify", {
bubbles: true,
composed: true,
detail: { kind, message }
})
);
};
const _MinusDialog = class _MinusDialog extends i {
constructor() {
super(...arguments);
this.closeDisabled = false;
this.heading = "";
this.open = false;
this.size = "medium";
this.hasFooter = false;
}
updated(changed) {
if (!changed.has("open")) {
return;
}
const dialog = this.renderRoot.querySelector("dialog");
if (!(dialog instanceof HTMLDialogElement)) {
return;
}
if (this.open && !dialog.open) {
dialog.showModal();
} else if (!this.open && dialog.open) {
dialog.close();
}
}
requestClose() {
if (!this.closeDisabled) {
this.dispatchEvent(new CustomEvent("minus-close", { bubbles: true, composed: true }));
}
}
handleCancel(event) {
event.preventDefault();
this.requestClose();
}
handleBackdropClick(event) {
event.stopPropagation();
if (!(event.target instanceof HTMLDialogElement)) {
return;
}
const bounds = event.target.getBoundingClientRect();
const inside = event.clientX >= bounds.left && event.clientX <= bounds.right && event.clientY >= bounds.top && event.clientY <= bounds.bottom;
if (!inside) {
this.requestClose();
}
}
stopInteractionPropagation(event) {
event.stopPropagation();
}
handleFooterChange(event) {
const slot = event.target;
if (slot instanceof HTMLSlotElement) {
this.hasFooter = slot.assignedElements().length > 0;
}
}
render() {
return b`
<dialog
class=${this.size}
aria-labelledby="minus115-dialog-heading"
@cancel=${this.handleCancel}
@click=${this.handleBackdropClick}
@contextmenu=${this.stopInteractionPropagation}
@copy=${this.stopInteractionPropagation}
@cut=${this.stopInteractionPropagation}
@keydown=${this.stopInteractionPropagation}
@keyup=${this.stopInteractionPropagation}
@mousedown=${this.stopInteractionPropagation}
@mouseup=${this.stopInteractionPropagation}
@paste=${this.stopInteractionPropagation}
@pointerdown=${this.stopInteractionPropagation}
@pointerup=${this.stopInteractionPropagation}
@wheel=${this.stopInteractionPropagation}
>
<article>
<header>
<h2 id="minus115-dialog-heading">${this.heading}</h2>
<button
class="close"
type="button"
aria-label="关闭"
?disabled=${this.closeDisabled}
@click=${this.requestClose}
>
×
</button>
</header>
<div class="body"><slot></slot></div>
<footer ?hidden=${!this.hasFooter}>
<slot name="footer" @slotchange=${this.handleFooterChange}></slot>
</footer>
</article>
</dialog>
`;
}
};
_MinusDialog.properties = {
closeDisabled: { attribute: "close-disabled", type: Boolean },
heading: { type: String },
open: { type: Boolean },
size: { type: String },
hasFooter: { state: true }
};
_MinusDialog.styles = i$3`
:host {
display: contents;
font-family: var(--minus115-font);
}
dialog {
width: min(640px, calc(100vw - 32px));
max-height: calc(100vh - 32px);
padding: 0;
overflow: hidden;
border: 1px solid var(--minus115-border);
border-radius: 10px;
color: var(--minus115-text);
background: var(--minus115-surface);
box-shadow: 0 16px 48px rgb(0 0 0 / 24%);
}
dialog.wide {
width: min(1120px, calc(100vw - 32px));
}
dialog.narrow {
width: min(420px, calc(100vw - 32px));
}
dialog::backdrop {
background: rgb(0 0 0 / 45%);
backdrop-filter: blur(1px);
}
article {
display: flex;
max-height: calc(100vh - 32px);
flex-direction: column;
}
header,
footer {
display: flex;
flex: none;
align-items: center;
padding: 14px 18px;
}
header {
justify-content: space-between;
border-bottom: 1px solid var(--minus115-border);
}
h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.close {
width: 32px;
height: 32px;
padding: 0;
border: 0;
border-radius: 6px;
color: var(--minus115-muted);
background: transparent;
cursor: pointer;
font-size: 22px;
line-height: 1;
}
.close:hover:not(:disabled) {
color: var(--minus115-text);
background: var(--minus115-hover);
}
.close:disabled {
cursor: not-allowed;
opacity: 0.45;
}
.body {
min-height: 0;
padding: 18px;
overflow: auto;
}
footer {
justify-content: flex-end;
border-top: 1px solid var(--minus115-border);
}
footer[hidden] {
display: none;
}
`;
let MinusDialog = _MinusDialog;
if (!customElements.get("minus115-dialog")) {
customElements.define("minus115-dialog", MinusDialog);
}
const createNode = (entry) => ({ ...entry });
const startBrowserDownload = (url, name) => {
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = name;
anchor.hidden = true;
document.body.appendChild(anchor);
anchor.click();
window.setTimeout(() => anchor.remove(), 100);
};
const replaceChildren = (nodes, pickCode, children) => nodes.map((node) => {
if (node.pickCode === pickCode) {
return { ...node, children };
}
return node.children ? { ...node, children: replaceChildren(node.children, pickCode, children) } : node;
});
const _FileDownloadDialog = class _FileDownloadDialog extends i {
constructor() {
super(...arguments);
this.downloadingCodes = [];
this.activeDirectoryRequests = 0;
this.directoryWaiters = [];
this.loadingDirectories = [];
this.nodes = [];
this.openState = false;
this.sessionId = 0;
}
openDialog() {
try {
const selected = getSelectedFiles();
if (selected.length === 0) {
throw new Error("请选择文件");
}
this.sessionId += 1;
this.loadingDirectories = [];
this.nodes = selected.map(createNode);
this.openState = true;
} catch (error) {
notify(this, "error", errorMessage(error));
}
}
closeDialog() {
this.sessionId += 1;
this.openState = false;
}
async listDirectory(directoryId) {
if (this.activeDirectoryRequests >= 4) {
await new Promise((resolve) => this.directoryWaiters.push(resolve));
}
this.activeDirectoryRequests += 1;
try {
return await listFolder(directoryId);
} finally {
this.activeDirectoryRequests -= 1;
this.directoryWaiters.shift()?.();
}
}
async loadDirectory(node) {
if (!node.isDirectory || node.children !== void 0 || this.loadingDirectories.includes(node.pickCode)) {
return;
}
const currentSessionId = this.sessionId;
this.loadingDirectories = [...this.loadingDirectories, node.pickCode];
try {
const children = (await this.listDirectory(node.directoryId)).map(createNode);
if (currentSessionId !== this.sessionId) {
return;
}
this.nodes = replaceChildren(this.nodes, node.pickCode, children);
} catch (error) {
if (currentSessionId === this.sessionId) {
notify(this, "error", errorMessage(error));
}
throw error;
} finally {
if (currentSessionId === this.sessionId) {
this.loadingDirectories = this.loadingDirectories.filter((code) => code !== node.pickCode);
}
}
}
handleToggle(event, node) {
const details = event.currentTarget;
if (details instanceof HTMLDetailsElement && details.open) {
void this.loadDirectory(node).catch(() => {
details.open = false;
});
}
}
async download(node) {
if (this.downloadingCodes.includes(node.pickCode)) {
return;
}
this.downloadingCodes = [...this.downloadingCodes, node.pickCode];
try {
const link = await getDownloadLink(node.pickCode);
startBrowserDownload(link.url, link.name);
} catch (error) {
notify(this, "error", errorMessage(error));
} finally {
this.downloadingCodes = this.downloadingCodes.filter((code) => code !== node.pickCode);
}
}
renderNodes(nodes) {
return b`
<ul>
${nodes.map(
(node) => node.isDirectory ? b`
<li>
<details @toggle=${(event) => this.handleToggle(event, node)}>
<summary>
${node.name}
${this.loadingDirectories.includes(node.pickCode) ? b`<span class="folder-status">读取中…</span>` : A}
</summary>
${node.children === void 0 ? A : node.children.length === 0 ? b`<p class="empty">空文件夹</p>` : this.renderNodes(node.children)}
</details>
</li>
` : b`
<li class="file">
<span class="file-name" title=${node.name}>${node.name}</span>
<button
class="link"
type="button"
?disabled=${this.downloadingCodes.includes(node.pickCode)}
@click=${() => void this.download(node)}
>
${this.downloadingCodes.includes(node.pickCode) ? "获取中…" : "下载"}
</button>
</li>
`
)}
</ul>
`;
}
render() {
return b`
<minus115-dialog
.heading=${"文件下载"}
.open=${this.openState}
@minus-close=${this.closeDialog}
>
<div class="tree-root">${this.renderNodes(this.nodes)}</div>
</minus115-dialog>
`;
}
};
_FileDownloadDialog.properties = {
downloadingCodes: { state: true },
loadingDirectories: { state: true },
nodes: { state: true },
openState: { state: true }
};
_FileDownloadDialog.styles = [
featureStyles,
i$3`
ul {
margin: 0;
padding-left: 22px;
list-style: none;
}
.tree-root {
max-height: 55vh;
padding-left: 0;
overflow: auto;
}
li {
margin: 3px 0;
}
summary {
padding: 5px 4px;
border-radius: 4px;
cursor: pointer;
user-select: none;
}
summary:hover {
background: var(--minus115-hover);
}
.file {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 4px 4px 4px 20px;
}
.file-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.folder-status {
margin-left: 8px;
color: var(--minus115-muted);
font-size: 12px;
}
`
];
let FileDownloadDialog = _FileDownloadDialog;
if (!customElements.get("minus115-file-download")) {
customElements.define("minus115-file-download", FileDownloadDialog);
}
const validateAria2RpcUrl = (value) => {
let url;
try {
url = new URL(value);
} catch (cause) {
throw new TypeError(`aria2 RPC 地址无效:${value}`, { cause });
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new TypeError(`aria2 RPC 地址仅支持 HTTP 或 HTTPS:${value}`);
}
};
const readAria2Error = (value) => {
if (!isObject(value)) {
return null;
}
return {
code: typeof value.code === "number" ? value.code : null,
message: optionalString(value, "message") || "aria2 返回未知错误"
};
};
const addAria2Uri = async (config, link) => {
const operation = `推送“${link.name}”到 aria2`;
validateAria2RpcUrl(config.rpcUrl);
const params = [
[link.url],
{
out: link.name,
"user-agent": navigator.userAgent
}
];
if (config.secret) {
params.unshift(`token:${config.secret}`);
}
const response = await requestTextWithRetry({
operation,
method: "POST",
url: config.rpcUrl,
data: JSON.stringify({
id: link.pickCode,
jsonrpc: "2.0",
method: "aria2.addUri",
params
}),
headers: {
"Content-Type": "application/json"
}
});
const json = parseObject(response.body, operation);
const rpcError = readAria2Error(json.error);
if (rpcError) {
const code = rpcError.code === null ? "" : `(代码 ${rpcError.code})`;
throw new ApplicationError(operation, `${rpcError.message}${code}`);
}
return requireString(json, "result", operation);
};
const validateIdmClientId = (value) => {
if (!/^\d+$/.test(value)) {
throw new TypeError(`IDM 客户标识必须是数字:${value || "<空>"}`);
}
};
const TEXT_ENCODER = new TextEncoder();
let nextSequence = 1;
let requestQueue = Promise.resolve();
const encodeField = (key, value) => {
const text = String(value);
return `${key}=${TEXT_ENCODER.encode(text).byteLength}:${text}`;
};
const readExtension = (filename) => {
const separator = filename.lastIndexOf(".");
return separator > 0 && separator < filename.length - 1 ? filename.slice(separator + 1).toUpperCase() : "";
};
const createRequestBody = (sequence, link, size) => {
const fields = [
encodeField(4, readExtension(link.name)),
encodeField(6, link.url),
encodeField(7, window.location.origin),
encodeField(11, `User-Agent: ${navigator.userAgent}
`),
encodeField(100, link.name),
encodeField(122, 4)
];
return `MSG#${sequence}#13#1#10241:${sequence + 1e3}:0:${Date.now()}:0:1:2:${size}:0,${fields.join(",")};`;
};
const send = async (config, link, size) => {
const operation = `推送“${link.name}”到 IDM`;
validateIdmClientId(config.clientId);
const sequence = nextSequence;
const response = await requestTextWithRetry({
operation,
method: "POST",
url: `http://127.0.0.1:1001/client/${config.clientId}?seq=${sequence}`,
data: createRequestBody(sequence, link, size)
});
if (!response.body.endsWith(`${sequence}:3;`)) {
throw new ApplicationError(operation, `IDM 扩展返回无法识别的响应:${response.body || "<空>"}`);
}
nextSequence += 1;
};
const addIdmDownload = (config, link, size) => {
const task = requestQueue.then(() => send(config, link, size));
requestQueue = task.catch(() => void 0);
return task;
};
const DEFAULT_SETTINGS = {
aria2RpcUrl: "http://127.0.0.1:6800/jsonrpc",
aria2Secret: "",
idmClientId: "1"
};
const directString = (value, key) => {
const direct = value[key];
return typeof direct === "string" ? direct : void 0;
};
const parseSettings = (stored) => {
if (!isObject(stored)) {
return { ...DEFAULT_SETTINGS };
}
return {
aria2RpcUrl: directString(stored, "aria2RpcUrl") ?? DEFAULT_SETTINGS.aria2RpcUrl,
aria2Secret: directString(stored, "aria2Secret") ?? DEFAULT_SETTINGS.aria2Secret,
idmClientId: directString(stored, "idmClientId") ?? DEFAULT_SETTINGS.idmClientId
};
};
const settings = Object.freeze(
parseSettings(GM_getValue("settings", null))
);
const saveSettings = (value) => {
validateAria2RpcUrl(value.aria2RpcUrl);
validateIdmClientId(value.idmClientId);
GM_setValue("settings", value);
};
const _SettingsDialog = class _SettingsDialog extends i {
constructor() {
super(...arguments);
this.draft = { ...settings };
this.openState = false;
}
openDialog() {
this.draft = { ...settings };
this.openState = true;
}
closeDialog() {
this.openState = false;
}
updateText(key, value) {
this.draft = { ...this.draft, [key]: value };
}
save() {
try {
saveSettings(this.draft);
window.location.reload();
} catch (error) {
notify(this, "error", errorMessage(error));
}
}
render() {
return b`
<minus115-dialog
.heading=${"115- 设置"}
.open=${this.openState}
@minus-close=${this.closeDialog}
>
<div class="settings">
<h3>aria2</h3>
<label>
<span>JSON-RPC 地址</span>
<input
type="text"
autocomplete="url"
placeholder="http://127.0.0.1:6800/jsonrpc"
.value=${this.draft.aria2RpcUrl}
@input=${(event) => {
if (event.currentTarget instanceof HTMLInputElement) {
this.updateText("aria2RpcUrl", event.currentTarget.value.trim());
}
}}
/>
</label>
<label>
<span>RPC 密钥(未设置时留空)</span>
<input
type="password"
autocomplete="off"
.value=${this.draft.aria2Secret}
@input=${(event) => {
if (event.currentTarget instanceof HTMLInputElement) {
this.updateText("aria2Secret", event.currentTarget.value);
}
}}
/>
</label>
<h3>IDM</h3>
<label>
<span>IDM Integration Module 扩展本地存储中的 client 值</span>
<input
type="text"
inputmode="numeric"
autocomplete="off"
placeholder="1"
.value=${this.draft.idmClientId}
@input=${(event) => {
if (event.currentTarget instanceof HTMLInputElement) {
this.updateText("idmClientId", event.currentTarget.value.trim());
}
}}
/>
</label>
</div>
<div class="actions" slot="footer">
<button type="button" @click=${this.closeDialog}>取消</button>
<button class="primary" type="button" @click=${this.save}>保存并刷新</button>
</div>
</minus115-dialog>
`;
}
};
_SettingsDialog.properties = {
draft: { state: true },
openState: { state: true }
};
_SettingsDialog.styles = [
featureStyles,
i$3`
.settings {
display: grid;
gap: 12px;
}
h3 {
margin: 0 0 4px;
font-size: 15px;
}
label {
display: grid;
gap: 6px;
}
label span {
color: var(--minus115-muted);
font-size: 13px;
}
`
];
let SettingsDialog = _SettingsDialog;
if (!customElements.get("minus115-settings-dialog")) {
customElements.define("minus115-settings-dialog", SettingsDialog);
}
const media = window.matchMedia("(prefers-color-scheme: dark)");
const _Minus115App = class _Minus115App extends i {
constructor() {
super(...arguments);
this.dark = media.matches;
this.nextToastId = 1;
this.toasts = [];
this.handleColorSchemeChange = (event) => {
this.dark = event.matches;
};
}
connectedCallback() {
super.connectedCallback();
media.addEventListener("change", this.handleColorSchemeChange);
}
disconnectedCallback() {
media.removeEventListener("change", this.handleColorSchemeChange);
super.disconnectedCallback();
}
openFileDownload() {
this.getFeature("minus115-file-download").openDialog();
}
openSettings() {
this.getFeature("minus115-settings-dialog").openDialog();
}
showNotification(kind, message) {
const toast = {
id: this.nextToastId,
kind,
message
};
this.nextToastId += 1;
this.toasts = [...this.toasts, toast];
window.setTimeout(
() => {
this.toasts = this.toasts.filter((item) => item.id !== toast.id);
},
kind === "error" ? 6e3 : 3e3
);
}
getFeature(selector) {
const feature = this.renderRoot.querySelector(selector);
if (!feature) {
throw new Error(`115- UI 组件尚未挂载:${selector}`);
}
return feature;
}
handleNotification(event) {
this.showNotification(event.detail.kind, event.detail.message);
}
render() {
return b`
<div data-theme=${this.dark ? "dark" : "light"} @minus115-notify=${this.handleNotification}>
<minus115-file-download></minus115-file-download>
<minus115-settings-dialog></minus115-settings-dialog>
</div>
<div class="toasts" aria-live="polite" aria-atomic="true">
${this.toasts.map(
(toast) => b`<div class="toast ${toast.kind}" role="status">${toast.message}</div>`
)}
</div>
`;
}
};
_Minus115App.properties = {
dark: { reflect: true, state: true, type: Boolean },
toasts: { state: true }
};
_Minus115App.styles = i$3`
:host {
--minus115-primary: #2777f8;
--minus115-danger: #d03050;
--minus115-text: #20252b;
--minus115-muted: #6f7782;
--minus115-border: #e2e5e9;
--minus115-surface: #fff;
--minus115-hover: #f3f5f7;
--minus115-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: contents;
}
:host([dark]) {
--minus115-text: #e5e7eb;
--minus115-muted: #a8adb5;
--minus115-border: #3d424a;
--minus115-surface: #202328;
--minus115-hover: #30343b;
}
.toasts {
position: fixed;
z-index: 2147483647;
top: 20px;
left: 50%;
display: grid;
width: min(440px, calc(100vw - 32px));
gap: 10px;
pointer-events: none;
transform: translateX(-50%);
}
.toast {
padding: 11px 14px;
border: 1px solid var(--minus115-border);
border-radius: 7px;
color: var(--minus115-text);
background: var(--minus115-surface);
box-shadow: 0 8px 28px rgb(0 0 0 / 20%);
font-family: var(--minus115-font);
line-height: 1.45;
pointer-events: auto;
}
.toast.success {
border-left: 4px solid #18a058;
}
.toast.error {
border-left: 4px solid var(--minus115-danger);
}
.toast.info {
border-left: 4px solid var(--minus115-primary);
}
`;
let Minus115App = _Minus115App;
if (!customElements.get("minus115-app")) {
customElements.define("minus115-app", Minus115App);
}
const COLLAPSED_STORAGE_KEY = "layout_collapsed_all_files";
const NEW_STORAGE_PAGE_PATH = "/storage/netdisk";
const redirectLegacyStoragePage = () => {
const isLegacyStoragePage = window.location.pathname === "/" && new URLSearchParams(window.location.search).get("mode") === "wangpan";
if (!isLegacyStoragePage) {
return false;
}
window.location.replace(NEW_STORAGE_PAGE_PATH);
return true;
};
const applyStoragePageDefaults = () => {
window.localStorage.setItem(COLLAPSED_STORAGE_KEY, "true");
};
const CONCURRENCY = 4;
const collectFiles = async (selected) => {
const files = selected.filter((entry) => !entry.isDirectory);
let directories = selected.filter((entry) => entry.isDirectory);
while (directories.length > 0) {
const nextDirectories = [];
for (let offset = 0; offset < directories.length; offset += CONCURRENCY) {
const batch = directories.slice(offset, offset + CONCURRENCY);
const children = await Promise.all(batch.map((entry) => listFolder(entry.directoryId)));
children.flat().forEach((entry) => {
if (entry.isDirectory) {
nextDirectories.push(entry);
} else {
files.push(entry);
}
});
}
directories = nextDirectories;
}
return Array.from(new Map(files.map((file) => [file.pickCode, file])).values());
};
const processSelected = async (selected, handleLink) => {
const files = await collectFiles(selected);
for (let offset = 0; offset < files.length; offset += CONCURRENCY) {
const batch = files.slice(offset, offset + CONCURRENCY);
await Promise.all(
batch.map(async (file) => {
const link = await getDownloadLink(file.pickCode);
await handleLink(link, file);
})
);
}
return files.length;
};
const pushSelectedToAria2 = (selected, config) => processSelected(selected, (link) => addAria2Uri(config, link).then(() => void 0));
const pushSelectedToIdm = (selected, config) => processSelected(selected, (link, file) => addIdmDownload(config, link, file.size));
const findButton = (root, text) => Array.from(root.querySelectorAll("button")).find(
(button) => button.textContent?.trim() === text
) ?? null;
const findUploadButton = (root) => {
const inputs = root.querySelectorAll(
'input[type="file"][multiple]:not([webkitdirectory])'
);
for (const input of inputs) {
const container = input.parentElement;
const directoryInput = container?.querySelector(':scope > input[type="file"][webkitdirectory]');
const button = container?.querySelector(":scope > button");
if (directoryInput && button instanceof HTMLButtonElement) {
return button;
}
}
return null;
};
const waitForButton = (text, timeoutMs) => new Promise((resolve, reject) => {
const observer = new MutationObserver(() => {
const button2 = findButton(document, text);
if (button2) {
observer.disconnect();
window.clearTimeout(timeoutId);
resolve(button2);
}
});
const timeoutId = window.setTimeout(() => {
observer.disconnect();
reject(new ApplicationError("打开离线下载", `等待官方“${text}”入口超时`));
}, timeoutMs);
observer.observe(document.body, { childList: true, subtree: true });
const button = findButton(document, text);
if (button) {
observer.disconnect();
window.clearTimeout(timeoutId);
resolve(button);
}
});
const findOfflineToolbarTarget = () => {
const uploadButton = findUploadButton(document);
const actions = uploadButton?.parentElement?.parentElement;
return actions instanceof HTMLElement ? actions : null;
};
const findSelectionDownloadTarget = () => {
const fileList = document.querySelector(".file-list-wrap");
const sticky = fileList?.closest(".sticky");
const selectionOverlay = sticky?.querySelector(".absolute.top-0.left-0.right-0");
const button = selectionOverlay?.querySelector("button");
return button instanceof HTMLButtonElement ? button : null;
};
const openOfficialOfflineDownload = async () => {
const uploadButton = findUploadButton(document);
if (!uploadButton) {
throw new ApplicationError("打开离线下载", "未找到主页面“上传”按钮");
}
uploadButton.dispatchEvent(
new MouseEvent("mouseover", {
bubbles: true,
cancelable: true
})
);
const menuButton = await waitForButton("添加云下载", 1e3);
menuButton.click();
};
const findSettingsTarget = () => findButton(document, "帮助")?.parentElement ?? null;
const createButton = (label, onClick) => {
const button = document.createElement("button");
button.type = "button";
button.textContent = label;
button.addEventListener("click", onClick);
return button;
};
const createSettingsButton = (app) => {
const button = createButton("115- 设置", () => app.openSettings());
button.id = "minus115-settings";
return button;
};
const findToolbarButtonTemplate = (target) => {
for (const item of Array.from(target.children).reverse()) {
const button = item instanceof HTMLButtonElement ? item : item.querySelector(":scope > button");
if (button && !button.id.startsWith("minus115-")) {
return button;
}
}
return null;
};
const createToolbarButton = (target, id, labelText, iconClass) => {
const source = findToolbarButtonTemplate(target);
if (!source) {
throw new Error(`115- 创建“${labelText}”按钮失败:控制栏中没有模板按钮`);
}
const button = source.cloneNode(true);
const label = document.createElement("span");
const children = [];
{
const icon = document.createElement("i");
icon.className = iconClass;
children.push(icon);
}
label.textContent = labelText;
children.push(label);
button.replaceChildren(...children);
button.disabled = false;
button.removeAttribute("aria-expanded");
button.removeAttribute("aria-haspopup");
button.id = id;
button.type = "button";
return button;
};
const createOfflineButton = (target) => {
const button = createToolbarButton(
target,
"minus115-offline",
"离线下载",
"icon-operate black ifo-linktask"
);
button.addEventListener("click", () => {
void openOfficialOfflineDownload().catch((error) => {
console.error("115- 打开官方离线下载弹窗失败", { error });
});
});
return button;
};
const bindTransferAction = (button, app, labelText, action) => {
const label = button.querySelector(":scope > span");
if (!label) {
throw new Error(`115- 绑定“${labelText}”失败:按钮缺少文字节点`);
}
button.addEventListener("click", () => {
if (button.disabled) {
return;
}
button.disabled = true;
label.textContent = "处理中…";
void action().then((count) => {
app.showNotification("success", `${labelText} 已处理 ${count} 个文件`);
}).catch((error) => {
console.error(`115- ${labelText} 失败`, { error });
app.showNotification("error", errorMessage(error));
}).finally(() => {
button.disabled = false;
label.textContent = labelText;
});
});
};
const createMenuButton = (id, labelText) => {
const button = document.createElement("button");
button.id = id;
button.type = "button";
button.className = "w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2";
const label = document.createElement("span");
label.textContent = labelText;
button.append(label);
return button;
};
const createDownloadDropdown = (template, app) => {
const wrapper = document.createElement("div");
wrapper.className = template.parentElement?.className || "relative";
const trigger = template.cloneNode(true);
trigger.id = "minus115-download";
trigger.type = "button";
trigger.disabled = false;
trigger.classList.add("gap-1");
const icon = trigger.querySelector(":scope > i");
if (icon) {
icon.className = "icon-operate ifo-download";
}
const labels = Array.from(trigger.querySelectorAll(":scope > span"));
const label = labels.at(-1);
if (!label) {
throw new Error("115- 创建直链下载按钮失败:模板按钮缺少文字节点");
}
label.textContent = "直链下载";
let arrow = trigger.querySelector(":scope > img");
if (!arrow) {
arrow = document.createElement("img");
arrow.alt = "";
arrow.width = 12;
arrow.height = 12;
arrow.className = "transition-transform";
arrow.src = "/arrow_down_small.svg";
trigger.append(arrow);
}
const getLinkButton = createMenuButton("minus115-get-link", "浏览器下载");
getLinkButton.addEventListener("click", () => app.openFileDownload());
const aria2Button = createMenuButton("minus115-aria2", "推送Aria2");
bindTransferAction(
aria2Button,
app,
"推送Aria2",
() => pushSelectedToAria2(getSelectedFiles(), {
rpcUrl: settings.aria2RpcUrl,
secret: settings.aria2Secret
})
);
const idmButton = createMenuButton("minus115-idm", "推送IDM");
bindTransferAction(
idmButton,
app,
"推送IDM",
() => pushSelectedToIdm(getSelectedFiles(), { clientId: settings.idmClientId })
);
const menuItems = document.createElement("div");
menuItems.className = "py-1";
menuItems.append(getLinkButton, aria2Button, idmButton);
const menuSurface = document.createElement("div");
menuSurface.className = "bg-white border border-gray-200 rounded-lg shadow-lg";
menuSurface.append(menuItems);
const menu = document.createElement("div");
menu.className = "absolute top-full left-0 pt-1 w-40 z-50";
menu.hidden = true;
menu.append(menuSurface);
const setOpen = (open) => {
menu.hidden = !open;
if (arrow instanceof HTMLImageElement) {
arrow.classList.toggle("rotate-180", open);
}
};
wrapper.addEventListener("mouseenter", () => setOpen(true));
wrapper.addEventListener("mouseleave", () => setOpen(false));
trigger.addEventListener("click", () => app.openFileDownload());
menu.addEventListener("click", () => setOpen(false));
wrapper.append(trigger, menu);
return wrapper;
};
const createSingleSelectionDownload = (target, app) => {
const uploadButton = findUploadButton(target);
if (!uploadButton) {
throw new Error("115- 创建单选直链下载按钮失败:未找到“上传”按钮");
}
return createDownloadDropdown(uploadButton, app);
};
const createMultipleSelectionDownload = (target, app) => {
if (!(target instanceof HTMLButtonElement)) {
throw new Error("115- 创建多选直链下载按钮失败:官方首个动作不是按钮");
}
return createDownloadDropdown(target, app);
};
const ASIDE_REPLACEMENTS = [
{
sourceHref: "/social/jianghu",
targetHref: "/storage/recentupload",
label: "最近上传",
icon: "/icons/storage/channel_recent_upload.svg"
},
{
sourceHref: "/chat",
targetHref: "/storage/clouddownload",
label: "云下载",
icon: "/icons/storage/channel_cloud_download.svg"
},
{
sourceHref: "/life/mine/vip",
targetHref: "/storage/recyclebin",
label: "回收站",
icon: "/icons/storage/channel_recycle_bin.svg"
}
];
const replaceAsideItem = (item) => {
const source = document.querySelector(
`aside nav a[href="${item.sourceHref}"]`
);
if (!source) {
return;
}
const replacement = source.cloneNode(true);
replacement.href = item.targetHref;
replacement.querySelectorAll("img").forEach((image) => {
image.alt = item.label;
image.src = item.icon;
});
const labels = Array.from(replacement.children).filter(
(child) => child instanceof HTMLSpanElement
);
const label = labels.shift();
if (!label) {
throw new ApplicationError("更新侧边栏", `入口“${item.label}”缺少文字节点`);
}
label.textContent = item.label;
labels.forEach((extra) => extra.remove());
source.replaceWith(replacement);
};
const readAsideAppearance = (link) => {
const label = link.querySelector(":scope > span");
const images = Array.from(link.querySelectorAll(":scope > img"));
if (!label || images.length === 0) {
throw new ApplicationError("更新侧边栏选中状态", "官方菜单项结构不完整");
}
return {
imageClassNames: images.map((image) => image.className),
labelClassName: label.className,
linkClassName: link.className,
style: link.getAttribute("style")
};
};
const applyAsideAppearance = (link, appearance) => {
const label = link.querySelector(":scope > span");
const images = Array.from(link.querySelectorAll(":scope > img"));
if (!label || images.length !== appearance.imageClassNames.length) {
throw new ApplicationError("更新侧边栏选中状态", "菜单项与官方模板结构不一致");
}
link.className = appearance.linkClassName;
if (appearance.style === null) {
link.removeAttribute("style");
} else {
link.setAttribute("style", appearance.style);
}
label.className = appearance.labelClassName;
images.forEach((image, index) => {
const className = appearance.imageClassNames[index];
if (className === void 0) {
throw new ApplicationError("更新侧边栏选中状态", "官方图标状态缺失");
}
image.className = className;
});
};
const syncAsideSelection = () => {
const links = Array.from(document.querySelectorAll("aside nav a"));
const activeTemplate = links.find(
(link) => link.classList.contains("bg-white") && link.classList.contains("text-blue-600")
);
const inactiveTemplate = links.find((link) => link.classList.contains("text-[#6F8396]"));
if (!activeTemplate || !inactiveTemplate) {
return;
}
const activeAppearance = readAsideAppearance(activeTemplate);
const inactiveAppearance = readAsideAppearance(inactiveTemplate);
links.forEach((link) => {
const appearance = new URL(link.href).pathname === window.location.pathname ? activeAppearance : inactiveAppearance;
applyAsideAppearance(link, appearance);
});
};
const syncAside = () => {
ASIDE_REPLACEMENTS.forEach(replaceAsideItem);
syncAsideSelection();
};
const FILE_CHECKBOX_SELECTOR = '.file-list-wrap .checkbox-area input[type="checkbox"]';
const PAGE_STRUCTURE_SELECTOR = [
"aside",
"button",
".file-list-wrap",
".sticky",
"#minus115-settings",
"#minus115-offline",
"#minus115-download"
].join(",");
const stopPropagation = (event) => {
event.stopPropagation();
};
class PersistentElement {
constructor(createElement, resolveTarget, insertElement, removeElement2) {
this.createElement = createElement;
this.resolveTarget = resolveTarget;
this.insertElement = insertElement;
this.removeElement = removeElement2;
this.element = null;
}
sync(enabled) {
if (!enabled) {
if (this.element) {
this.removeElement(this.element);
}
this.element = null;
return;
}
const target = this.resolveTarget();
if (!target) {
if (this.element) {
this.removeElement(this.element);
this.element = null;
}
return;
}
if (this.element?.isConnected && (target.contains(this.element) || this.element.contains(target))) {
return;
}
if (this.element) {
this.removeElement(this.element);
}
this.element = this.createElement(target);
this.element.addEventListener("mousedown", stopPropagation);
this.insertElement(target, this.element);
}
}
const appendElement = (target, element) => {
target.append(element);
};
const removeElement = (element) => {
element.remove();
};
const insertAfterUpload = (target, element) => {
const uploadButton = findUploadButton(target);
const uploadContainer = uploadButton?.parentElement;
if (!uploadContainer || uploadContainer.parentElement !== target) {
throw new Error("115- 插入直链下载按钮失败:未找到“上传”按钮容器");
}
uploadContainer.after(element);
};
const hookedTargets = new WeakMap();
const replaceSelectionDownload = (target, element) => {
const container = target.parentElement;
if (!container) {
throw new Error("115- 替换多选下载按钮失败:官方按钮缺少容器");
}
const toolbar = container.parentElement;
if (!toolbar) {
throw new Error("115- 替换多选下载按钮失败:官方操作栏不存在");
}
const overlay = toolbar.parentElement?.parentElement;
if (!overlay) {
throw new Error("115- 替换多选下载按钮失败:官方多选浮层不存在");
}
const overflowHidden = toolbar.classList.contains("overflow-hidden");
const zIndexRaised = overlay.classList.contains("z-10");
toolbar.classList.remove("overflow-hidden");
if (zIndexRaised) {
overlay.classList.replace("z-10", "z-50");
}
container.replaceWith(element);
hookedTargets.set(element, {
container,
overflowHidden,
overlay,
toolbar,
zIndexRaised
});
};
const restoreSelectionDownload = (element) => {
const target = hookedTargets.get(element);
if (target) {
if (element.isConnected) {
element.replaceWith(target.container);
}
if (target.overflowHidden && target.toolbar.isConnected) {
target.toolbar.classList.add("overflow-hidden");
}
if (target.zIndexRaised && target.overlay.isConnected) {
target.overlay.classList.replace("z-50", "z-10");
}
}
element.remove();
};
const containsPageStructure = (node) => {
const element = node instanceof Element ? node : node.parentElement;
return Boolean(
element?.matches(PAGE_STRUCTURE_SELECTOR) || element?.querySelector(PAGE_STRUCTURE_SELECTOR)
);
};
const affectsPageStructure = (records) => records.some(
(record) => [...record.addedNodes, ...record.removedNodes].some(containsPageStructure)
);
const setupStoragePage = (app) => {
const mounts = [
new PersistentElement(
() => createSettingsButton(app),
findSettingsTarget,
appendElement,
removeElement
),
new PersistentElement(
createOfflineButton,
findOfflineToolbarTarget,
appendElement,
removeElement
)
];
const singleDownloadMount = new PersistentElement(
(target) => createSingleSelectionDownload(target, app),
findOfflineToolbarTarget,
insertAfterUpload,
removeElement
);
const multipleDownloadMount = new PersistentElement(
(target) => createMultipleSelectionDownload(target, app),
findSelectionDownloadTarget,
replaceSelectionDownload,
restoreSelectionDownload
);
const sync = () => {
try {
syncAside();
mounts.forEach((mount) => mount.sync(true));
const selectedCount = getSelectedFileCount();
singleDownloadMount.sync(selectedCount === 1);
multipleDownloadMount.sync(selectedCount > 1);
} catch (error) {
console.error("115- 同步页面功能失败", { error });
}
};
let scheduled = false;
const scheduleSync = () => {
if (scheduled) {
return;
}
scheduled = true;
queueMicrotask(() => {
scheduled = false;
sync();
});
};
sync();
const observer = new MutationObserver((records) => {
if (affectsPageStructure(records)) {
scheduleSync();
}
});
observer.observe(document.body, { childList: true, subtree: true });
document.addEventListener(
"change",
(event) => {
const target = event.target;
if (target instanceof HTMLInputElement && target.matches(FILE_CHECKBOX_SELECTOR)) {
scheduleSync();
}
},
true
);
document.addEventListener("click", (event) => {
const target = event.target instanceof Element ? event.target.closest("aside nav a") : null;
if (target) {
window.setTimeout(scheduleSync, 0);
}
});
window.addEventListener("popstate", scheduleSync);
};
const adBlockCss = 'div:has(>img[src*="act.115.com/api/1.0/web/1.0/spotlight/imgload"]),div:has(>img[alt=购买VIP]){display:none!important}';
importCSS(adBlockCss);
const videoPlayerCss = ".minus115-video-player-page,.minus115-video-player-page body{overflow:hidden!important}.minus115-video-player{position:fixed!important;z-index:2147483647!important;inset:0!important;width:auto!important;height:auto!important}";
importCSS(videoPlayerCss);
const isVideoPlayerPage = window.location.pathname.startsWith("/players/video/");
const isStoragePage = window.location.pathname === "/storage" || window.location.pathname.startsWith("/storage/");
const setupCurrentPage = () => {
if (isVideoPlayerPage) {
setupVideoPlayer();
} else if (isStoragePage) {
const app = new Minus115App();
document.body.appendChild(app);
setupStoragePage(app);
}
};
if (window.top === window.self && !redirectLegacyStoragePage()) {
if (isStoragePage) {
applyStoragePageDefaults();
}
if (document.body) {
setupCurrentPage();
} else {
document.addEventListener("DOMContentLoaded", setupCurrentPage, { once: true });
}
}
})();