Sleazy Fork is available in English.

tanhuazu-helper

tanhuazu.com 探花族论坛助手

// ==UserScript==
// @name         tanhuazu-helper
// @namespace    tanhuazu-helper.xyjtyskfydhqss.none
// @version      0.1.4
// @author       xyjtyskfydhqss
// @description  tanhuazu.com 探花族论坛助手
// @license      MIT
// @icon         https://www.tanhuazu.com/favicon.ico
// @include      https://www.tanhuazu.com/*
// @include      https://tanhuazu.com/*
// @require      https://unpkg.com/react@18.2.0/umd/react.production.min.js
// @require      https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js
// @connect      self
// @connect      obdown.com
// @grant        GM.xmlHttpRequest
// @grant        GM_notification
// @grant        GM_openInTab
// ==/UserScript==

(e=>{const t=document.createElement("style");t.dataset.source="vite-plugin-monkey",t.innerText=e,document.head.appendChild(t)})(" ._preview-img-wrapper_1v8wn_1{z-index:500}._preview-img-wrapper_1v8wn_1 img{max-height:100%;max-width:100%}.tanhuazu-download-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;background-color:transparent;border:1px solid transparent;cursor:pointer;transition:all .2s cubic-bezier(.645,.045,.355,1);user-select:none;touch-action:manipulation;line-height:1.57142857;font-size:16px;height:40px;border-radius:8px;color:#fff;background-color:#1677ff;outline:none;position:absolute;left:calc(100% + 10px);width:100px;top:0;height:auto;width:auto;font-size:30px;padding:5px;display:flex;align-items:center;justify-content:center;text-decoration:none}.tanhuazu-download-btn:hover,.tanhuazu-download-btn:visited{text-decoration:none}.tanhuazu .block-body .message:first-child .message-attribution{font-size:30px}.tanhuazu .structItem.structItem--thread.last-clicked,.tanhuazu .block-row.last-clicked{background-color:#ff8c00;color:#fff}.tanhuazu .structItem.structItem--thread.last-clicked a,.tanhuazu .block-row.last-clicked a{color:#fff} ");

(function(require$$0, require$$0$1) {
  "use strict";
  function getDefaultExportFromCjs(x) {
    return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
  }
  var delayExports = {};
  var delay$2 = {
    get exports() {
      return delayExports;
    },
    set exports(v) {
      delayExports = v;
    }
  };
  const randomInteger = (minimum, maximum) => Math.floor(Math.random() * (maximum - minimum + 1) + minimum);
  const createAbortError = () => {
    const error = new Error("Delay aborted");
    error.name = "AbortError";
    return error;
  };
  const createDelay = ({ clearTimeout: defaultClear, setTimeout: set, willResolve }) => (ms, { value, signal } = {}) => {
    if (signal && signal.aborted) {
      return Promise.reject(createAbortError());
    }
    let timeoutId;
    let settle;
    let rejectFn;
    const clear = defaultClear || clearTimeout;
    const signalListener = () => {
      clear(timeoutId);
      rejectFn(createAbortError());
    };
    const cleanup = () => {
      if (signal) {
        signal.removeEventListener("abort", signalListener);
      }
    };
    const delayPromise = new Promise((resolve, reject) => {
      settle = () => {
        cleanup();
        if (willResolve) {
          resolve(value);
        } else {
          reject(value);
        }
      };
      rejectFn = reject;
      timeoutId = (set || setTimeout)(settle, ms);
    });
    if (signal) {
      signal.addEventListener("abort", signalListener, { once: true });
    }
    delayPromise.clear = () => {
      clear(timeoutId);
      timeoutId = null;
      settle();
    };
    return delayPromise;
  };
  const createWithTimers = (clearAndSet) => {
    const delay2 = createDelay({ ...clearAndSet, willResolve: true });
    delay2.reject = createDelay({ ...clearAndSet, willResolve: false });
    delay2.range = (minimum, maximum, options) => delay2(randomInteger(minimum, maximum), options);
    return delay2;
  };
  const delay$1 = createWithTimers();
  delay$1.createWithTimers = createWithTimers;
  delay$2.exports = delay$1;
  delayExports.default = delay$1;
  function idle() {
    return new Promise((resolve) => {
      requestIdleCallback(() => resolve(void 0));
    });
  }
  const APP_NAME = "tanhuazu-helper";
  function logWithLabel(...args) {
    const [msg, ...rest] = args;
    if (typeof msg === "string") {
      console.log(`[${APP_NAME}]: ${msg}`, ...rest);
    } else {
      console.log(`[${APP_NAME}]: `, msg, ...rest);
    }
  }
  async function handleReplyWait() {
    while (true) {
      await waitOverlayAndProcess();
      await delayExports(1e3);
    }
  }
  async function waitOverlayAndProcess() {
    let overlay;
    let msgEl;
    let title = "";
    let msg = "";
    const hasWarningOverlay = async () => {
      var _a, _b, _c;
      await idle();
      overlay = document.querySelector(
        ".overlay-container.is-active .overlay"
      );
      if (!overlay)
        return;
      title = ((_b = (_a = overlay.querySelector(".overlay-title")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) ?? "";
      msgEl = overlay == null ? void 0 : overlay.querySelector(
        ".overlay-content .blockMessage"
      );
      msg = ((_c = msgEl == null ? void 0 : msgEl.textContent) == null ? void 0 : _c.trim()) ?? "";
      if (title === "哎呀!我们遇到了一些问题。" && msg && msg.includes("您必须等待") && msg.includes("后才可以继续执行此操作")) {
        return true;
      }
    };
    while (!await hasWarningOverlay()) {
      await delayExports(500);
    }
    let seconds = Number(
      /您必须等待 (\d+) 秒后才可以继续执行此操作。/.exec(msg)[1]
    );
    if (!seconds || isNaN(seconds))
      return;
    while (seconds > 0) {
      if (!document.querySelector(".overlay-container.is-active .overlay")) {
        return;
      }
      await delayExports(1e3);
      seconds--;
      const rest = seconds >= 60 ? `${Math.floor(seconds / 60)} 分 ${seconds % 60} 秒` : `${seconds} 秒`;
      msgEl.textContent = `您必须等待 ${rest} 后才可以继续执行此操作。`;
    }
    await delayExports(1e3);
    GM_notification({
      title: "tanhuazu.com 可以继续操作了",
      text: document.title,
      onclick() {
        GM_openInTab(location.href, {
          active: true,
          insert: true
        });
      }
    });
  }
  function parseRawHeaders(h2) {
    const s2 = h2.trim();
    if (!s2) {
      return new Headers();
    }
    const array = s2.split("\r\n").map((value) => {
      let s3 = value.split(":");
      return [s3[0].trim(), s3[1].trim()];
    });
    return new Headers(array);
  }
  function parseGMResponse(res) {
    const r2 = new Response(res.response, {
      statusText: res.statusText,
      status: res.status,
      headers: parseRawHeaders(res.responseHeaders)
    });
    Object.defineProperty(r2, "url", {
      value: res.finalUrl
    });
    return r2;
  }
  async function GM_fetch(input, init) {
    const request = new Request(input, init);
    let data;
    if (init == null ? void 0 : init.body) {
      data = await request.text();
    }
    return await XHR(request, init, data);
  }
  function XHR(request, init, data) {
    return new Promise((resolve, reject) => {
      if (request.signal && request.signal.aborted) {
        return reject(new DOMException("Aborted", "AbortError"));
      }
      GM.xmlHttpRequest({
        url: request.url,
        method: gmXHRMethod(request.method.toUpperCase()),
        headers: Object.fromEntries(new Headers(init == null ? void 0 : init.headers).entries()),
        data,
        responseType: "blob",
        onload(res) {
          resolve(parseGMResponse(res));
        },
        onabort() {
          reject(new DOMException("Aborted", "AbortError"));
        },
        ontimeout() {
          reject(new TypeError("Network request failed, timeout"));
        },
        onerror(err) {
          reject(new TypeError("Failed to fetch: " + err.finalUrl));
        }
      });
    });
  }
  const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "TRACE", "OPTIONS", "CONNECT"];
  function includes(array, element) {
    return array.includes(element);
  }
  function gmXHRMethod(method) {
    if (includes(httpMethods, method)) {
      return method;
    }
    throw new Error(`unsupported http method ${method}`);
  }
  class HTTPError extends Error {
    constructor(response, request, options) {
      const code = response.status || response.status === 0 ? response.status : "";
      const title = response.statusText || "";
      const status = `${code} ${title}`.trim();
      const reason = status ? `status code ${status}` : "an unknown error";
      super(`Request failed with ${reason}`);
      Object.defineProperty(this, "response", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, "request", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, "options", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      this.name = "HTTPError";
      this.response = response;
      this.request = request;
      this.options = options;
    }
  }
  class TimeoutError extends Error {
    constructor(request) {
      super("Request timed out");
      Object.defineProperty(this, "request", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      this.name = "TimeoutError";
      this.request = request;
    }
  }
  const isObject$1 = (value) => value !== null && typeof value === "object";
  const validateAndMerge = (...sources) => {
    for (const source of sources) {
      if ((!isObject$1(source) || Array.isArray(source)) && typeof source !== "undefined") {
        throw new TypeError("The `options` argument must be an object");
      }
    }
    return deepMerge({}, ...sources);
  };
  const mergeHeaders = (source1 = {}, source2 = {}) => {
    const result = new globalThis.Headers(source1);
    const isHeadersInstance = source2 instanceof globalThis.Headers;
    const source = new globalThis.Headers(source2);
    for (const [key, value] of source.entries()) {
      if (isHeadersInstance && value === "undefined" || value === void 0) {
        result.delete(key);
      } else {
        result.set(key, value);
      }
    }
    return result;
  };
  const deepMerge = (...sources) => {
    let returnValue = {};
    let headers = {};
    for (const source of sources) {
      if (Array.isArray(source)) {
        if (!Array.isArray(returnValue)) {
          returnValue = [];
        }
        returnValue = [...returnValue, ...source];
      } else if (isObject$1(source)) {
        for (let [key, value] of Object.entries(source)) {
          if (isObject$1(value) && key in returnValue) {
            value = deepMerge(returnValue[key], value);
          }
          returnValue = { ...returnValue, [key]: value };
        }
        if (isObject$1(source.headers)) {
          headers = mergeHeaders(headers, source.headers);
          returnValue.headers = headers;
        }
      }
    }
    return returnValue;
  };
  const supportsRequestStreams = (() => {
    let duplexAccessed = false;
    let hasContentType = false;
    const supportsReadableStream = typeof globalThis.ReadableStream === "function";
    if (supportsReadableStream) {
      hasContentType = new globalThis.Request("https://a.com", {
        body: new globalThis.ReadableStream(),
        method: "POST",
        // @ts-expect-error - Types are outdated.
        get duplex() {
          duplexAccessed = true;
          return "half";
        }
      }).headers.has("Content-Type");
    }
    return duplexAccessed && !hasContentType;
  })();
  const supportsAbortController = typeof globalThis.AbortController === "function";
  const supportsResponseStreams = typeof globalThis.ReadableStream === "function";
  const supportsFormData = typeof globalThis.FormData === "function";
  const requestMethods = ["get", "post", "put", "patch", "head", "delete"];
  const responseTypes = {
    json: "application/json",
    text: "text/*",
    formData: "multipart/form-data",
    arrayBuffer: "*/*",
    blob: "*/*"
  };
  const maxSafeTimeout = 2147483647;
  const stop = Symbol("stop");
  const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
  const retryMethods = ["get", "put", "head", "delete", "options", "trace"];
  const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
  const retryAfterStatusCodes = [413, 429, 503];
  const defaultRetryOptions = {
    limit: 2,
    methods: retryMethods,
    statusCodes: retryStatusCodes,
    afterStatusCodes: retryAfterStatusCodes,
    maxRetryAfter: Number.POSITIVE_INFINITY,
    backoffLimit: Number.POSITIVE_INFINITY
  };
  const normalizeRetryOptions = (retry = {}) => {
    if (typeof retry === "number") {
      return {
        ...defaultRetryOptions,
        limit: retry
      };
    }
    if (retry.methods && !Array.isArray(retry.methods)) {
      throw new Error("retry.methods must be an array");
    }
    if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
      throw new Error("retry.statusCodes must be an array");
    }
    return {
      ...defaultRetryOptions,
      ...retry,
      afterStatusCodes: retryAfterStatusCodes
    };
  };
  async function timeout(request, abortController, options) {
    return new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        if (abortController) {
          abortController.abort();
        }
        reject(new TimeoutError(request));
      }, options.timeout);
      void options.fetch(request).then(resolve).catch(reject).then(() => {
        clearTimeout(timeoutId);
      });
    });
  }
  const isDomExceptionSupported = Boolean(globalThis.DOMException);
  function composeAbortError(signal) {
    if (isDomExceptionSupported) {
      return new DOMException((signal == null ? void 0 : signal.reason) ?? "The operation was aborted.", "AbortError");
    }
    const error = new Error((signal == null ? void 0 : signal.reason) ?? "The operation was aborted.");
    error.name = "AbortError";
    return error;
  }
  async function delay(ms, { signal }) {
    return new Promise((resolve, reject) => {
      if (signal) {
        if (signal.aborted) {
          reject(composeAbortError(signal));
          return;
        }
        signal.addEventListener("abort", handleAbort, { once: true });
      }
      function handleAbort() {
        reject(composeAbortError(signal));
        clearTimeout(timeoutId);
      }
      const timeoutId = setTimeout(() => {
        signal == null ? void 0 : signal.removeEventListener("abort", handleAbort);
        resolve();
      }, ms);
    });
  }
  class Ky {
    // eslint-disable-next-line @typescript-eslint/promise-function-async
    static create(input, options) {
      const ky2 = new Ky(input, options);
      const fn = async () => {
        if (ky2._options.timeout > maxSafeTimeout) {
          throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
        }
        await Promise.resolve();
        let response = await ky2._fetch();
        for (const hook of ky2._options.hooks.afterResponse) {
          const modifiedResponse = await hook(ky2.request, ky2._options, ky2._decorateResponse(response.clone()));
          if (modifiedResponse instanceof globalThis.Response) {
            response = modifiedResponse;
          }
        }
        ky2._decorateResponse(response);
        if (!response.ok && ky2._options.throwHttpErrors) {
          let error = new HTTPError(response, ky2.request, ky2._options);
          for (const hook of ky2._options.hooks.beforeError) {
            error = await hook(error);
          }
          throw error;
        }
        if (ky2._options.onDownloadProgress) {
          if (typeof ky2._options.onDownloadProgress !== "function") {
            throw new TypeError("The `onDownloadProgress` option must be a function");
          }
          if (!supportsResponseStreams) {
            throw new Error("Streams are not supported in your environment. `ReadableStream` is missing.");
          }
          return ky2._stream(response.clone(), ky2._options.onDownloadProgress);
        }
        return response;
      };
      const isRetriableMethod = ky2._options.retry.methods.includes(ky2.request.method.toLowerCase());
      const result = isRetriableMethod ? ky2._retry(fn) : fn();
      for (const [type, mimeType] of Object.entries(responseTypes)) {
        result[type] = async () => {
          ky2.request.headers.set("accept", ky2.request.headers.get("accept") || mimeType);
          const awaitedResult = await result;
          const response = awaitedResult.clone();
          if (type === "json") {
            if (response.status === 204) {
              return "";
            }
            const arrayBuffer = await response.clone().arrayBuffer();
            const responseSize = arrayBuffer.byteLength;
            if (responseSize === 0) {
              return "";
            }
            if (options.parseJson) {
              return options.parseJson(await response.text());
            }
          }
          return response[type]();
        };
      }
      return result;
    }
    // eslint-disable-next-line complexity
    constructor(input, options = {}) {
      Object.defineProperty(this, "request", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, "abortController", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, "_retryCount", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: 0
      });
      Object.defineProperty(this, "_input", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      Object.defineProperty(this, "_options", {
        enumerable: true,
        configurable: true,
        writable: true,
        value: void 0
      });
      this._input = input;
      this._options = {
        // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
        credentials: this._input.credentials || "same-origin",
        ...options,
        headers: mergeHeaders(this._input.headers, options.headers),
        hooks: deepMerge({
          beforeRequest: [],
          beforeRetry: [],
          beforeError: [],
          afterResponse: []
        }, options.hooks),
        method: normalizeRequestMethod(options.method ?? this._input.method),
        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
        prefixUrl: String(options.prefixUrl || ""),
        retry: normalizeRetryOptions(options.retry),
        throwHttpErrors: options.throwHttpErrors !== false,
        timeout: typeof options.timeout === "undefined" ? 1e4 : options.timeout,
        fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
      };
      if (typeof this._input !== "string" && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
        throw new TypeError("`input` must be a string, URL, or Request");
      }
      if (this._options.prefixUrl && typeof this._input === "string") {
        if (this._input.startsWith("/")) {
          throw new Error("`input` must not begin with a slash when using `prefixUrl`");
        }
        if (!this._options.prefixUrl.endsWith("/")) {
          this._options.prefixUrl += "/";
        }
        this._input = this._options.prefixUrl + this._input;
      }
      if (supportsAbortController) {
        this.abortController = new globalThis.AbortController();
        if (this._options.signal) {
          const originalSignal = this._options.signal;
          this._options.signal.addEventListener("abort", () => {
            this.abortController.abort(originalSignal.reason);
          });
        }
        this._options.signal = this.abortController.signal;
      }
      if (supportsRequestStreams) {
        this._options.duplex = "half";
      }
      this.request = new globalThis.Request(this._input, this._options);
      if (this._options.searchParams) {
        const textSearchParams = typeof this._options.searchParams === "string" ? this._options.searchParams.replace(/^\?/, "") : new URLSearchParams(this._options.searchParams).toString();
        const searchParams = "?" + textSearchParams;
        const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
        if ((supportsFormData && this._options.body instanceof globalThis.FormData || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers["content-type"])) {
          this.request.headers.delete("content-type");
        }
        this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
      }
      if (this._options.json !== void 0) {
        this._options.body = JSON.stringify(this._options.json);
        this.request.headers.set("content-type", this._options.headers.get("content-type") ?? "application/json");
        this.request = new globalThis.Request(this.request, { body: this._options.body });
      }
    }
    _calculateRetryDelay(error) {
      this._retryCount++;
      if (this._retryCount < this._options.retry.limit && !(error instanceof TimeoutError)) {
        if (error instanceof HTTPError) {
          if (!this._options.retry.statusCodes.includes(error.response.status)) {
            return 0;
          }
          const retryAfter = error.response.headers.get("Retry-After");
          if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
            let after = Number(retryAfter);
            if (Number.isNaN(after)) {
              after = Date.parse(retryAfter) - Date.now();
            } else {
              after *= 1e3;
            }
            if (typeof this._options.retry.maxRetryAfter !== "undefined" && after > this._options.retry.maxRetryAfter) {
              return 0;
            }
            return after;
          }
          if (error.response.status === 413) {
            return 0;
          }
        }
        const BACKOFF_FACTOR = 0.3;
        return Math.min(this._options.retry.backoffLimit, BACKOFF_FACTOR * 2 ** (this._retryCount - 1) * 1e3);
      }
      return 0;
    }
    _decorateResponse(response) {
      if (this._options.parseJson) {
        response.json = async () => this._options.parseJson(await response.text());
      }
      return response;
    }
    async _retry(fn) {
      try {
        return await fn();
      } catch (error) {
        const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
        if (ms !== 0 && this._retryCount > 0) {
          await delay(ms, { signal: this._options.signal });
          for (const hook of this._options.hooks.beforeRetry) {
            const hookResult = await hook({
              request: this.request,
              options: this._options,
              error,
              retryCount: this._retryCount
            });
            if (hookResult === stop) {
              return;
            }
          }
          return this._retry(fn);
        }
        throw error;
      }
    }
    async _fetch() {
      for (const hook of this._options.hooks.beforeRequest) {
        const result = await hook(this.request, this._options);
        if (result instanceof Request) {
          this.request = result;
          break;
        }
        if (result instanceof Response) {
          return result;
        }
      }
      if (this._options.timeout === false) {
        return this._options.fetch(this.request.clone());
      }
      return timeout(this.request.clone(), this.abortController, this._options);
    }
    /* istanbul ignore next */
    _stream(response, onDownloadProgress) {
      const totalBytes = Number(response.headers.get("content-length")) || 0;
      let transferredBytes = 0;
      if (response.status === 204) {
        if (onDownloadProgress) {
          onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
        }
        return new globalThis.Response(null, {
          status: response.status,
          statusText: response.statusText,
          headers: response.headers
        });
      }
      return new globalThis.Response(new globalThis.ReadableStream({
        async start(controller) {
          const reader = response.body.getReader();
          if (onDownloadProgress) {
            onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
          }
          async function read() {
            const { done, value } = await reader.read();
            if (done) {
              controller.close();
              return;
            }
            if (onDownloadProgress) {
              transferredBytes += value.byteLength;
              const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
              onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
            }
            controller.enqueue(value);
            await read();
          }
          await read();
        }
      }), {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers
      });
    }
  }
  /*! MIT License © Sindre Sorhus */
  const createInstance = (defaults) => {
    const ky2 = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
    for (const method of requestMethods) {
      ky2[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
    }
    ky2.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
    ky2.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
    ky2.stop = stop;
    return ky2;
  };
  const ky = createInstance();
  const ky$1 = ky;
  const kyfetch = ky$1.extend({ fetch: GM_fetch });
  async function fetchMagnetLink() {
    var _a, _b, _c;
    const query = () => document.querySelector(
      `.block-body article.message .message-body a[href*="obdown.com"][href*=".torrent"]`
    );
    const timeout2 = performance.now() + 5e3;
    while (!query() && performance.now() < timeout2) {
      await delayExports(500);
    }
    const a2 = query();
    if (!a2)
      return;
    const torrentPageUrl = a2.getAttribute("href");
    if (!torrentPageUrl)
      return;
    console.log("[tanhuazu-helper]: torrent download page %s", torrentPageUrl);
    const html = await kyfetch.get(torrentPageUrl, {
      retry: 5
    }).text();
    const p2 = new DOMParser();
    const doc = p2.parseFromString(html, "text/html");
    const magnetSpan = Array.from(
      doc.querySelectorAll("span.text-secondary")
    ).filter((span) => {
      var _a2;
      return ((_a2 = span.textContent) == null ? void 0 : _a2.trim()) === "MAGENT";
    })[0];
    const magnet = (_b = (_a = magnetSpan == null ? void 0 : magnetSpan.nextElementSibling) == null ? void 0 : _a.querySelector(`a[href^="magnet:?xt="]`)) == null ? void 0 : _b.getAttribute("href");
    console.log("[tanhuazu-helper]: magnet link %s", magnet);
    if (!magnet)
      return;
    const firstMessage = document.querySelector(
      ".block-body article.message"
    );
    firstMessage.style.position = "relative";
    const btnShare = (_c = firstMessage.querySelector('a[aria-label="分享"]')) == null ? void 0 : _c.parentElement;
    const btnDl = document.createElement("li");
    btnDl.innerHTML = `
    <a href="${magnet}" class="message-attribution-gadget" title="磁力链接">
      <svg viewBox="64 64 896 896" focusable="false" data-icon="download" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg>
    </a>
  `;
    btnShare == null ? void 0 : btnShare.insertAdjacentElement("afterend", btnDl);
    btnShare == null ? void 0 : btnShare.remove();
    const createBtn = () => {
      const btn = document.createElement("a");
      btn.href = magnet;
      btn.innerHTML = `
      <svg viewBox="64 64 896 896" focusable="false" data-icon="download" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg>
      <span>下载</span>
    `;
      btn.className = "tanhuazu-download-btn";
      return btn;
    };
    const topBtn = createBtn();
    firstMessage.appendChild(topBtn);
    const bottomBtn = createBtn();
    bottomBtn.style.top = "unset";
    bottomBtn.style.bottom = "0";
    firstMessage.appendChild(bottomBtn);
  }
  var jsxRuntimeExports = {};
  var jsxRuntime = {
    get exports() {
      return jsxRuntimeExports;
    },
    set exports(v) {
      jsxRuntimeExports = v;
    }
  };
  var reactJsxRuntime_production_min = {};
  /**
   * @license React
   * react-jsx-runtime.production.min.js
   *
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  var f$1 = require$$0, k$1 = Symbol.for("react.element"), l$2 = Symbol.for("react.fragment"), m$2 = Object.prototype.hasOwnProperty, n$2 = f$1.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p$2 = { key: true, ref: true, __self: true, __source: true };
  function q$1(c2, a2, g) {
    var b, d = {}, e2 = null, h2 = null;
    void 0 !== g && (e2 = "" + g);
    void 0 !== a2.key && (e2 = "" + a2.key);
    void 0 !== a2.ref && (h2 = a2.ref);
    for (b in a2)
      m$2.call(a2, b) && !p$2.hasOwnProperty(b) && (d[b] = a2[b]);
    if (c2 && c2.defaultProps)
      for (b in a2 = c2.defaultProps, a2)
        void 0 === d[b] && (d[b] = a2[b]);
    return { $$typeof: k$1, type: c2, key: e2, ref: h2, props: d, _owner: n$2.current };
  }
  reactJsxRuntime_production_min.Fragment = l$2;
  reactJsxRuntime_production_min.jsx = q$1;
  reactJsxRuntime_production_min.jsxs = q$1;
  (function(module) {
    {
      module.exports = reactJsxRuntime_production_min;
    }
  })(jsxRuntime);
  const jsx = jsxRuntimeExports.jsx;
  var createRoot;
  var m$1 = require$$0$1;
  {
    createRoot = m$1.createRoot;
    m$1.hydrateRoot;
  }
  const e$1 = Symbol(), t$1 = Symbol(), r$1 = "a", n$1 = "w";
  let o = (e2, t2) => new Proxy(e2, t2);
  const s = Object.getPrototypeOf, c = /* @__PURE__ */ new WeakMap(), l$1 = (e2) => e2 && (c.has(e2) ? c.get(e2) : s(e2) === Object.prototype || s(e2) === Array.prototype), f = (e2) => "object" == typeof e2 && null !== e2, i = (e2) => {
    if (Array.isArray(e2))
      return Array.from(e2);
    const t2 = Object.getOwnPropertyDescriptors(e2);
    return Object.values(t2).forEach((e3) => {
      e3.configurable = true;
    }), Object.create(s(e2), t2);
  }, u$1 = (e2) => e2[t$1] || e2, a = (s2, c2, f2, p2) => {
    if (!l$1(s2))
      return s2;
    let g = p2 && p2.get(s2);
    if (!g) {
      const e2 = u$1(s2);
      g = ((e3) => Object.values(Object.getOwnPropertyDescriptors(e3)).some((e4) => !e4.configurable && !e4.writable))(e2) ? [e2, i(e2)] : [e2], null == p2 || p2.set(s2, g);
    }
    const [y2, h2] = g;
    let w2 = f2 && f2.get(y2);
    return w2 && w2[1].f === !!h2 || (w2 = ((o2, s3) => {
      const c3 = { f: s3 };
      let l2 = false;
      const f3 = (e2, t2) => {
        if (!l2) {
          let s4 = c3[r$1].get(o2);
          if (s4 || (s4 = {}, c3[r$1].set(o2, s4)), e2 === n$1)
            s4[n$1] = true;
          else {
            let r2 = s4[e2];
            r2 || (r2 = /* @__PURE__ */ new Set(), s4[e2] = r2), r2.add(t2);
          }
        }
      }, i2 = { get: (e2, n2) => n2 === t$1 ? o2 : (f3("k", n2), a(Reflect.get(e2, n2), c3[r$1], c3.c)), has: (t2, n2) => n2 === e$1 ? (l2 = true, c3[r$1].delete(o2), true) : (f3("h", n2), Reflect.has(t2, n2)), getOwnPropertyDescriptor: (e2, t2) => (f3("o", t2), Reflect.getOwnPropertyDescriptor(e2, t2)), ownKeys: (e2) => (f3(n$1), Reflect.ownKeys(e2)) };
      return s3 && (i2.set = i2.deleteProperty = () => false), [i2, c3];
    })(y2, !!h2), w2[1].p = o(h2 || y2, w2[0]), f2 && f2.set(y2, w2)), w2[1][r$1] = c2, w2[1].c = f2, w2[1].p;
  }, p$1 = (e2, t2, r2, o2) => {
    if (Object.is(e2, t2))
      return false;
    if (!f(e2) || !f(t2))
      return true;
    const s2 = r2.get(u$1(e2));
    if (!s2)
      return true;
    if (o2) {
      const r3 = o2.get(e2);
      if (r3 && r3.n === t2)
        return r3.g;
      o2.set(e2, { n: t2, g: false });
    }
    let c2 = null;
    try {
      for (const r3 of s2.h || [])
        if (c2 = Reflect.has(e2, r3) !== Reflect.has(t2, r3), c2)
          return c2;
      if (true === s2[n$1]) {
        if (c2 = ((e3, t3) => {
          const r3 = Reflect.ownKeys(e3), n2 = Reflect.ownKeys(t3);
          return r3.length !== n2.length || r3.some((e4, t4) => e4 !== n2[t4]);
        })(e2, t2), c2)
          return c2;
      } else
        for (const r3 of s2.o || [])
          if (c2 = !!Reflect.getOwnPropertyDescriptor(e2, r3) != !!Reflect.getOwnPropertyDescriptor(t2, r3), c2)
            return c2;
      for (const n2 of s2.k || [])
        if (c2 = p$1(e2[n2], t2[n2], r2, o2), c2)
          return c2;
      return null === c2 && (c2 = true), c2;
    } finally {
      o2 && o2.set(e2, { n: t2, g: c2 });
    }
  }, y = (e2) => l$1(e2) && e2[t$1] || null, h$1 = (e2, t2 = true) => {
    c.set(e2, t2);
  }, w = (e2, t2, r2) => {
    const o2 = [], s2 = /* @__PURE__ */ new WeakSet(), c2 = (e3, l2) => {
      if (s2.has(e3))
        return;
      f(e3) && s2.add(e3);
      const i2 = f(e3) && t2.get(u$1(e3));
      if (i2) {
        var a2, p2;
        if (null == (a2 = i2.h) || a2.forEach((e4) => {
          const t3 = `:has(${String(e4)})`;
          o2.push(l2 ? [...l2, t3] : [t3]);
        }), true === i2[n$1]) {
          const e4 = ":ownKeys";
          o2.push(l2 ? [...l2, e4] : [e4]);
        } else {
          var g;
          null == (g = i2.o) || g.forEach((e4) => {
            const t3 = `:hasOwn(${String(e4)})`;
            o2.push(l2 ? [...l2, t3] : [t3]);
          });
        }
        null == (p2 = i2.k) || p2.forEach((t3) => {
          r2 && !("value" in (Object.getOwnPropertyDescriptor(e3, t3) || {})) || c2(e3[t3], l2 ? [...l2, t3] : [t3]);
        });
      } else
        l2 && o2.push(l2);
    };
    return c2(e2), o2;
  };
  const isObject = (x) => typeof x === "object" && x !== null;
  const proxyStateMap = /* @__PURE__ */ new WeakMap();
  const refSet = /* @__PURE__ */ new WeakSet();
  const buildProxyFunction = (objectIs = Object.is, newProxy = (target, handler) => new Proxy(target, handler), canProxy = (x) => isObject(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer), defaultHandlePromise = (promise) => {
    switch (promise.status) {
      case "fulfilled":
        return promise.value;
      case "rejected":
        throw promise.reason;
      default:
        throw promise;
    }
  }, snapCache = /* @__PURE__ */ new WeakMap(), createSnapshot = (target, version, handlePromise = defaultHandlePromise) => {
    const cache = snapCache.get(target);
    if ((cache == null ? void 0 : cache[0]) === version) {
      return cache[1];
    }
    const snap = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
    h$1(snap, true);
    snapCache.set(target, [version, snap]);
    Reflect.ownKeys(target).forEach((key) => {
      if (Object.getOwnPropertyDescriptor(snap, key)) {
        return;
      }
      const value = Reflect.get(target, key);
      const desc = {
        value,
        enumerable: true,
        // This is intentional to avoid copying with proxy-compare.
        // It's still non-writable, so it avoids assigning a value.
        configurable: true
      };
      if (refSet.has(value)) {
        h$1(value, false);
      } else if (value instanceof Promise) {
        delete desc.value;
        desc.get = () => handlePromise(value);
      } else if (proxyStateMap.has(value)) {
        const [target2, ensureVersion] = proxyStateMap.get(
          value
        );
        desc.value = createSnapshot(
          target2,
          ensureVersion(),
          handlePromise
        );
      }
      Object.defineProperty(snap, key, desc);
    });
    return snap;
  }, proxyCache = /* @__PURE__ */ new WeakMap(), versionHolder = [1, 1], proxyFunction = (initialObject) => {
    if (!isObject(initialObject)) {
      throw new Error("object required");
    }
    const found = proxyCache.get(initialObject);
    if (found) {
      return found;
    }
    let version = versionHolder[0];
    const listeners = /* @__PURE__ */ new Set();
    const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => {
      if (version !== nextVersion) {
        version = nextVersion;
        listeners.forEach((listener) => listener(op, nextVersion));
      }
    };
    let checkVersion = versionHolder[1];
    const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => {
      if (checkVersion !== nextCheckVersion && !listeners.size) {
        checkVersion = nextCheckVersion;
        propProxyStates.forEach(([propProxyState]) => {
          const propVersion = propProxyState[1](nextCheckVersion);
          if (propVersion > version) {
            version = propVersion;
          }
        });
      }
      return version;
    };
    const createPropListener = (prop) => (op, nextVersion) => {
      const newOp = [...op];
      newOp[1] = [prop, ...newOp[1]];
      notifyUpdate(newOp, nextVersion);
    };
    const propProxyStates = /* @__PURE__ */ new Map();
    const addPropListener = (prop, propProxyState) => {
      if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && propProxyStates.has(prop)) {
        throw new Error("prop listener already exists");
      }
      if (listeners.size) {
        const remove = propProxyState[3](createPropListener(prop));
        propProxyStates.set(prop, [propProxyState, remove]);
      } else {
        propProxyStates.set(prop, [propProxyState]);
      }
    };
    const removePropListener = (prop) => {
      var _a;
      const entry = propProxyStates.get(prop);
      if (entry) {
        propProxyStates.delete(prop);
        (_a = entry[1]) == null ? void 0 : _a.call(entry);
      }
    };
    const addListener = (listener) => {
      listeners.add(listener);
      if (listeners.size === 1) {
        propProxyStates.forEach(([propProxyState, prevRemove], prop) => {
          if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && prevRemove) {
            throw new Error("remove already exists");
          }
          const remove = propProxyState[3](createPropListener(prop));
          propProxyStates.set(prop, [propProxyState, remove]);
        });
      }
      const removeListener = () => {
        listeners.delete(listener);
        if (listeners.size === 0) {
          propProxyStates.forEach(([propProxyState, remove], prop) => {
            if (remove) {
              remove();
              propProxyStates.set(prop, [propProxyState]);
            }
          });
        }
      };
      return removeListener;
    };
    const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject));
    const handler = {
      deleteProperty(target, prop) {
        const prevValue = Reflect.get(target, prop);
        removePropListener(prop);
        const deleted = Reflect.deleteProperty(target, prop);
        if (deleted) {
          notifyUpdate(["delete", [prop], prevValue]);
        }
        return deleted;
      },
      set(target, prop, value, receiver) {
        const hasPrevValue = Reflect.has(target, prop);
        const prevValue = Reflect.get(target, prop, receiver);
        if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) {
          return true;
        }
        removePropListener(prop);
        if (isObject(value)) {
          value = y(value) || value;
        }
        let nextValue = value;
        if (value instanceof Promise) {
          value.then((v) => {
            value.status = "fulfilled";
            value.value = v;
            notifyUpdate(["resolve", [prop], v]);
          }).catch((e2) => {
            value.status = "rejected";
            value.reason = e2;
            notifyUpdate(["reject", [prop], e2]);
          });
        } else {
          if (!proxyStateMap.has(value) && canProxy(value)) {
            nextValue = proxyFunction(value);
          }
          const childProxyState = !refSet.has(nextValue) && proxyStateMap.get(nextValue);
          if (childProxyState) {
            addPropListener(prop, childProxyState);
          }
        }
        Reflect.set(target, prop, nextValue, receiver);
        notifyUpdate(["set", [prop], value, prevValue]);
        return true;
      }
    };
    const proxyObject = newProxy(baseObject, handler);
    proxyCache.set(initialObject, proxyObject);
    const proxyState = [
      baseObject,
      ensureVersion,
      createSnapshot,
      addListener
    ];
    proxyStateMap.set(proxyObject, proxyState);
    Reflect.ownKeys(initialObject).forEach((key) => {
      const desc = Object.getOwnPropertyDescriptor(
        initialObject,
        key
      );
      if ("value" in desc) {
        proxyObject[key] = initialObject[key];
        delete desc.value;
        delete desc.writable;
      }
      Object.defineProperty(baseObject, key, desc);
    });
    return proxyObject;
  }) => [
    // public functions
    proxyFunction,
    // shared state
    proxyStateMap,
    refSet,
    // internal things
    objectIs,
    newProxy,
    canProxy,
    defaultHandlePromise,
    snapCache,
    createSnapshot,
    proxyCache,
    versionHolder
  ];
  const [defaultProxyFunction] = buildProxyFunction();
  function proxy(initialObject = {}) {
    return defaultProxyFunction(initialObject);
  }
  function subscribe(proxyObject, callback, notifyInSync) {
    const proxyState = proxyStateMap.get(proxyObject);
    if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && !proxyState) {
      console.warn("Please use proxy object");
    }
    let promise;
    const ops = [];
    const addListener = proxyState[3];
    let isListenerActive = false;
    const listener = (op) => {
      ops.push(op);
      if (notifyInSync) {
        callback(ops.splice(0));
        return;
      }
      if (!promise) {
        promise = Promise.resolve().then(() => {
          promise = void 0;
          if (isListenerActive) {
            callback(ops.splice(0));
          }
        });
      }
    };
    const removeListener = addListener(listener);
    isListenerActive = true;
    return () => {
      isListenerActive = false;
      removeListener();
    };
  }
  function snapshot(proxyObject, handlePromise) {
    const proxyState = proxyStateMap.get(proxyObject);
    if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && !proxyState) {
      console.warn("Please use proxy object");
    }
    const [target, ensureVersion, createSnapshot] = proxyState;
    return createSnapshot(target, ensureVersion(), handlePromise);
  }
  function ref(obj) {
    refSet.add(obj);
    return obj;
  }
  var shimExports = {};
  var shim = {
    get exports() {
      return shimExports;
    },
    set exports(v) {
      shimExports = v;
    }
  };
  var useSyncExternalStoreShim_production_min = {};
  /**
   * @license React
   * use-sync-external-store-shim.production.min.js
   *
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  var e = require$$0;
  function h(a2, b) {
    return a2 === b && (0 !== a2 || 1 / a2 === 1 / b) || a2 !== a2 && b !== b;
  }
  var k = "function" === typeof Object.is ? Object.is : h, l = e.useState, m = e.useEffect, n = e.useLayoutEffect, p = e.useDebugValue;
  function q(a2, b) {
    var d = b(), f2 = l({ inst: { value: d, getSnapshot: b } }), c2 = f2[0].inst, g = f2[1];
    n(function() {
      c2.value = d;
      c2.getSnapshot = b;
      r(c2) && g({ inst: c2 });
    }, [a2, d, b]);
    m(function() {
      r(c2) && g({ inst: c2 });
      return a2(function() {
        r(c2) && g({ inst: c2 });
      });
    }, [a2]);
    p(d);
    return d;
  }
  function r(a2) {
    var b = a2.getSnapshot;
    a2 = a2.value;
    try {
      var d = b();
      return !k(a2, d);
    } catch (f2) {
      return true;
    }
  }
  function t(a2, b) {
    return b();
  }
  var u = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? t : q;
  useSyncExternalStoreShim_production_min.useSyncExternalStore = void 0 !== e.useSyncExternalStore ? e.useSyncExternalStore : u;
  (function(module) {
    {
      module.exports = useSyncExternalStoreShim_production_min;
    }
  })(shim);
  const useSyncExternalStoreExports = /* @__PURE__ */ getDefaultExportFromCjs(shimExports);
  const { use } = require$$0;
  const { useSyncExternalStore } = useSyncExternalStoreExports;
  const useAffectedDebugValue = (state2, affected) => {
    const pathList = require$$0.useRef();
    require$$0.useEffect(() => {
      pathList.current = w(state2, affected, true);
    });
    require$$0.useDebugValue(pathList.current);
  };
  const targetCache = /* @__PURE__ */ new WeakMap();
  function useSnapshot(proxyObject, options) {
    const notifyInSync = options == null ? void 0 : options.sync;
    const lastSnapshot = require$$0.useRef();
    const lastAffected = require$$0.useRef();
    let inRender = true;
    const currSnapshot = useSyncExternalStore(
      require$$0.useCallback(
        (callback) => {
          const unsub = subscribe(proxyObject, callback, notifyInSync);
          callback();
          return unsub;
        },
        [proxyObject, notifyInSync]
      ),
      () => {
        const nextSnapshot = snapshot(proxyObject, use);
        try {
          if (!inRender && lastSnapshot.current && lastAffected.current && !p$1(
            lastSnapshot.current,
            nextSnapshot,
            lastAffected.current,
            /* @__PURE__ */ new WeakMap()
          )) {
            return lastSnapshot.current;
          }
        } catch (e2) {
        }
        return nextSnapshot;
      },
      () => snapshot(proxyObject, use)
    );
    inRender = false;
    const currAffected = /* @__PURE__ */ new WeakMap();
    require$$0.useEffect(() => {
      lastSnapshot.current = currSnapshot;
      lastAffected.current = currAffected;
    });
    if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production") {
      useAffectedDebugValue(currSnapshot, currAffected);
    }
    const proxyCache = require$$0.useMemo(() => /* @__PURE__ */ new WeakMap(), []);
    return a(
      currSnapshot,
      currAffected,
      proxyCache,
      targetCache
    );
  }
  const previewImgWrapper = "_preview-img-wrapper_1v8wn_1";
  const styles = {
    previewImgWrapper
  };
  function showPreviewImgWhenHover(container) {
    if (!container)
      return;
    container.onmouseover = async (e2) => {
      const src = e2.target;
      if (src.tagName.toLowerCase() !== "a")
        return;
      const u2 = new URL(src.href, location.href);
      if (!u2.pathname.startsWith("/threads/"))
        return;
      const threadUrl = u2.pathname.split("/").slice(0, 3).join("/") + "/";
      logWithLabel("hover: %s", threadUrl);
      state.a = ref(src);
      state.threadUrl = threadUrl;
      const imgs = await getPreviewImg(threadUrl);
      logWithLabel("fetched imgs", imgs);
      if (state.threadUrl === threadUrl) {
        state.imgs = imgs;
      }
      if (!root) {
        const div = document.createElement("div");
        div.classList.add("preview-img-root");
        document.body.appendChild(div);
        root = createRoot(div);
        root.render(/* @__PURE__ */ jsx(PreviewImg, {}));
      }
    };
    container.onmouseout = function(e2) {
      state.a = void 0;
      state.threadUrl = "";
      state.imgs = [];
    };
  }
  async function getPreviewImg(threadUrl) {
    const html = await ky$1.get(threadUrl, { cache: "force-cache" }).text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    const srcs = Array.from(
      doc.querySelectorAll(
        ".block-body .message:first-child .bbImageWrapper img"
      )
    ).map((img) => img.src);
    return srcs;
  }
  const state = proxy({
    threadUrl: "",
    a: void 0,
    imgs: []
  });
  function PreviewImg() {
    const { imgs, a: a2 } = useSnapshot(state);
    const x = require$$0.useMemo(() => {
      const rect = a2 == null ? void 0 : a2.getBoundingClientRect();
      const x2 = ((rect == null ? void 0 : rect.right) || 0) + 50;
      return x2;
    }, [a2]);
    if (!imgs.length)
      return null;
    return /* @__PURE__ */ jsx(
      "div",
      {
        className: styles.previewImgWrapper,
        style: {
          position: "fixed",
          left: x,
          top: 10,
          width: `calc(100vw - ${x}px - 20px)`,
          height: "calc(100vh - 20px)",
          overflow: "hidden"
        },
        children: imgs.slice(0, 1).map((src) => {
          return /* @__PURE__ */ jsx("img", { src, alt: "" }, src);
        })
      }
    );
  }
  let root;
  function postListMain() {
    const container = getThreadListContainer();
    if (!container)
      return;
    container.onclick = (e2) => {
      markLastClicked(e2);
      fixPostLink(e2);
    };
    showPreviewImgWhenHover(container);
  }
  function isSearchResultPage() {
    if (!/^\/search\/\d+\//.test(location.pathname))
      return false;
    const u2 = new URL(location.href);
    if (u2.searchParams.get("searchform") === "1")
      return false;
    return true;
  }
  function getThreadListContainer() {
    let el;
    if (el = document.querySelector(
      [
        '.block[data-type="thread"]',
        '.block[data-widget-key="whats_new_new_posts"]',
        // /what's new
        '.p-body-pageContent:has(> form[action^="/watched/threads"])'
        // /watched/threads
      ].join(",")
    )) {
      return el;
    }
    if (isSearchResultPage() && (el = document.querySelector(".p-body-pageContent"))) {
      return el;
    }
    if (el = document.querySelector(".p-body-pageContent")) {
      return el;
    }
  }
  function fixPostLink(e2) {
    const src = e2.target;
    if (src.tagName.toLowerCase() !== "a")
      return;
    const u2 = new URL(src.href, location.href);
    if (!u2.pathname.startsWith("/threads/"))
      return;
    if (u2.pathname.includes("unread")) {
      e2.preventDefault();
      const newLink = src.href.replace(/unread/, "");
      GM_openInTab(newLink);
    }
  }
  function markLastClicked(e2) {
    var _a;
    const lineSelector = ".structItem.structItem--thread, .block-row";
    const cur = e2.target.closest(lineSelector);
    cur == null ? void 0 : cur.classList.add("last-clicked");
    (_a = e2.currentTarget) == null ? void 0 : _a.querySelectorAll(lineSelector).forEach((item) => {
      item !== cur && item.classList.remove("last-clicked");
    });
  }
  const style = "";
  main();
  function main() {
    document.body.classList.add("tanhuazu");
    const p2 = location.pathname;
    if (p2.startsWith("/threads/")) {
      handleReplyWait();
      fetchMagnetLink();
      return;
    }
    if (p2.startsWith("/forums/") || // 论坛
    p2 === "/whats-new/" || // 最新消息
    p2.startsWith("/whats-new/posts/") || // 新帖
    p2.startsWith("/find-threads/") || // 查找主题(mine, 已回复, 未回复)
    p2.startsWith("/watched/threads") || // 关注主题
    isSearchResultPage() || // 搜索结果
    p2.startsWith("/tags/")) {
      return postListMain();
    }
  }
})(React, ReactDOM);