115-

面向 115 网盘 Web 端的轻量用户脚本,专注下载、离线任务与界面体验

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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 });
    }
  }

})();