// ==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);