tanhuazu-helper

tanhuazu.com 探花族论坛助手

  1. // ==UserScript==
  2. // @name tanhuazu-helper
  3. // @namespace tanhuazu-helper.xyjtyskfydhqss.none
  4. // @version 0.1.4
  5. // @author xyjtyskfydhqss
  6. // @description tanhuazu.com 探花族论坛助手
  7. // @license MIT
  8. // @icon https://www.tanhuazu.com/favicon.ico
  9. // @include https://www.tanhuazu.com/*
  10. // @include https://tanhuazu.com/*
  11. // @require https://unpkg.com/react@18.2.0/umd/react.production.min.js
  12. // @require https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js
  13. // @connect self
  14. // @connect obdown.com
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM_notification
  17. // @grant GM_openInTab
  18. // ==/UserScript==
  19.  
  20. (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} ");
  21.  
  22. (function(require$$0, require$$0$1) {
  23. "use strict";
  24. function getDefaultExportFromCjs(x) {
  25. return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
  26. }
  27. var delayExports = {};
  28. var delay$2 = {
  29. get exports() {
  30. return delayExports;
  31. },
  32. set exports(v) {
  33. delayExports = v;
  34. }
  35. };
  36. const randomInteger = (minimum, maximum) => Math.floor(Math.random() * (maximum - minimum + 1) + minimum);
  37. const createAbortError = () => {
  38. const error = new Error("Delay aborted");
  39. error.name = "AbortError";
  40. return error;
  41. };
  42. const createDelay = ({ clearTimeout: defaultClear, setTimeout: set, willResolve }) => (ms, { value, signal } = {}) => {
  43. if (signal && signal.aborted) {
  44. return Promise.reject(createAbortError());
  45. }
  46. let timeoutId;
  47. let settle;
  48. let rejectFn;
  49. const clear = defaultClear || clearTimeout;
  50. const signalListener = () => {
  51. clear(timeoutId);
  52. rejectFn(createAbortError());
  53. };
  54. const cleanup = () => {
  55. if (signal) {
  56. signal.removeEventListener("abort", signalListener);
  57. }
  58. };
  59. const delayPromise = new Promise((resolve, reject) => {
  60. settle = () => {
  61. cleanup();
  62. if (willResolve) {
  63. resolve(value);
  64. } else {
  65. reject(value);
  66. }
  67. };
  68. rejectFn = reject;
  69. timeoutId = (set || setTimeout)(settle, ms);
  70. });
  71. if (signal) {
  72. signal.addEventListener("abort", signalListener, { once: true });
  73. }
  74. delayPromise.clear = () => {
  75. clear(timeoutId);
  76. timeoutId = null;
  77. settle();
  78. };
  79. return delayPromise;
  80. };
  81. const createWithTimers = (clearAndSet) => {
  82. const delay2 = createDelay({ ...clearAndSet, willResolve: true });
  83. delay2.reject = createDelay({ ...clearAndSet, willResolve: false });
  84. delay2.range = (minimum, maximum, options) => delay2(randomInteger(minimum, maximum), options);
  85. return delay2;
  86. };
  87. const delay$1 = createWithTimers();
  88. delay$1.createWithTimers = createWithTimers;
  89. delay$2.exports = delay$1;
  90. delayExports.default = delay$1;
  91. function idle() {
  92. return new Promise((resolve) => {
  93. requestIdleCallback(() => resolve(void 0));
  94. });
  95. }
  96. const APP_NAME = "tanhuazu-helper";
  97. function logWithLabel(...args) {
  98. const [msg, ...rest] = args;
  99. if (typeof msg === "string") {
  100. console.log(`[${APP_NAME}]: ${msg}`, ...rest);
  101. } else {
  102. console.log(`[${APP_NAME}]: `, msg, ...rest);
  103. }
  104. }
  105. async function handleReplyWait() {
  106. while (true) {
  107. await waitOverlayAndProcess();
  108. await delayExports(1e3);
  109. }
  110. }
  111. async function waitOverlayAndProcess() {
  112. let overlay;
  113. let msgEl;
  114. let title = "";
  115. let msg = "";
  116. const hasWarningOverlay = async () => {
  117. var _a, _b, _c;
  118. await idle();
  119. overlay = document.querySelector(
  120. ".overlay-container.is-active .overlay"
  121. );
  122. if (!overlay)
  123. return;
  124. title = ((_b = (_a = overlay.querySelector(".overlay-title")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) ?? "";
  125. msgEl = overlay == null ? void 0 : overlay.querySelector(
  126. ".overlay-content .blockMessage"
  127. );
  128. msg = ((_c = msgEl == null ? void 0 : msgEl.textContent) == null ? void 0 : _c.trim()) ?? "";
  129. if (title === "哎呀!我们遇到了一些问题。" && msg && msg.includes("您必须等待") && msg.includes("后才可以继续执行此操作")) {
  130. return true;
  131. }
  132. };
  133. while (!await hasWarningOverlay()) {
  134. await delayExports(500);
  135. }
  136. let seconds = Number(
  137. /您必须等待 (\d+) 秒后才可以继续执行此操作。/.exec(msg)[1]
  138. );
  139. if (!seconds || isNaN(seconds))
  140. return;
  141. while (seconds > 0) {
  142. if (!document.querySelector(".overlay-container.is-active .overlay")) {
  143. return;
  144. }
  145. await delayExports(1e3);
  146. seconds--;
  147. const rest = seconds >= 60 ? `${Math.floor(seconds / 60)} ${seconds % 60} 秒` : `${seconds} 秒`;
  148. msgEl.textContent = `您必须等待 ${rest} 后才可以继续执行此操作。`;
  149. }
  150. await delayExports(1e3);
  151. GM_notification({
  152. title: "tanhuazu.com 可以继续操作了",
  153. text: document.title,
  154. onclick() {
  155. GM_openInTab(location.href, {
  156. active: true,
  157. insert: true
  158. });
  159. }
  160. });
  161. }
  162. function parseRawHeaders(h2) {
  163. const s2 = h2.trim();
  164. if (!s2) {
  165. return new Headers();
  166. }
  167. const array = s2.split("\r\n").map((value) => {
  168. let s3 = value.split(":");
  169. return [s3[0].trim(), s3[1].trim()];
  170. });
  171. return new Headers(array);
  172. }
  173. function parseGMResponse(res) {
  174. const r2 = new Response(res.response, {
  175. statusText: res.statusText,
  176. status: res.status,
  177. headers: parseRawHeaders(res.responseHeaders)
  178. });
  179. Object.defineProperty(r2, "url", {
  180. value: res.finalUrl
  181. });
  182. return r2;
  183. }
  184. async function GM_fetch(input, init) {
  185. const request = new Request(input, init);
  186. let data;
  187. if (init == null ? void 0 : init.body) {
  188. data = await request.text();
  189. }
  190. return await XHR(request, init, data);
  191. }
  192. function XHR(request, init, data) {
  193. return new Promise((resolve, reject) => {
  194. if (request.signal && request.signal.aborted) {
  195. return reject(new DOMException("Aborted", "AbortError"));
  196. }
  197. GM.xmlHttpRequest({
  198. url: request.url,
  199. method: gmXHRMethod(request.method.toUpperCase()),
  200. headers: Object.fromEntries(new Headers(init == null ? void 0 : init.headers).entries()),
  201. data,
  202. responseType: "blob",
  203. onload(res) {
  204. resolve(parseGMResponse(res));
  205. },
  206. onabort() {
  207. reject(new DOMException("Aborted", "AbortError"));
  208. },
  209. ontimeout() {
  210. reject(new TypeError("Network request failed, timeout"));
  211. },
  212. onerror(err) {
  213. reject(new TypeError("Failed to fetch: " + err.finalUrl));
  214. }
  215. });
  216. });
  217. }
  218. const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "TRACE", "OPTIONS", "CONNECT"];
  219. function includes(array, element) {
  220. return array.includes(element);
  221. }
  222. function gmXHRMethod(method) {
  223. if (includes(httpMethods, method)) {
  224. return method;
  225. }
  226. throw new Error(`unsupported http method ${method}`);
  227. }
  228. class HTTPError extends Error {
  229. constructor(response, request, options) {
  230. const code = response.status || response.status === 0 ? response.status : "";
  231. const title = response.statusText || "";
  232. const status = `${code} ${title}`.trim();
  233. const reason = status ? `status code ${status}` : "an unknown error";
  234. super(`Request failed with ${reason}`);
  235. Object.defineProperty(this, "response", {
  236. enumerable: true,
  237. configurable: true,
  238. writable: true,
  239. value: void 0
  240. });
  241. Object.defineProperty(this, "request", {
  242. enumerable: true,
  243. configurable: true,
  244. writable: true,
  245. value: void 0
  246. });
  247. Object.defineProperty(this, "options", {
  248. enumerable: true,
  249. configurable: true,
  250. writable: true,
  251. value: void 0
  252. });
  253. this.name = "HTTPError";
  254. this.response = response;
  255. this.request = request;
  256. this.options = options;
  257. }
  258. }
  259. class TimeoutError extends Error {
  260. constructor(request) {
  261. super("Request timed out");
  262. Object.defineProperty(this, "request", {
  263. enumerable: true,
  264. configurable: true,
  265. writable: true,
  266. value: void 0
  267. });
  268. this.name = "TimeoutError";
  269. this.request = request;
  270. }
  271. }
  272. const isObject$1 = (value) => value !== null && typeof value === "object";
  273. const validateAndMerge = (...sources) => {
  274. for (const source of sources) {
  275. if ((!isObject$1(source) || Array.isArray(source)) && typeof source !== "undefined") {
  276. throw new TypeError("The `options` argument must be an object");
  277. }
  278. }
  279. return deepMerge({}, ...sources);
  280. };
  281. const mergeHeaders = (source1 = {}, source2 = {}) => {
  282. const result = new globalThis.Headers(source1);
  283. const isHeadersInstance = source2 instanceof globalThis.Headers;
  284. const source = new globalThis.Headers(source2);
  285. for (const [key, value] of source.entries()) {
  286. if (isHeadersInstance && value === "undefined" || value === void 0) {
  287. result.delete(key);
  288. } else {
  289. result.set(key, value);
  290. }
  291. }
  292. return result;
  293. };
  294. const deepMerge = (...sources) => {
  295. let returnValue = {};
  296. let headers = {};
  297. for (const source of sources) {
  298. if (Array.isArray(source)) {
  299. if (!Array.isArray(returnValue)) {
  300. returnValue = [];
  301. }
  302. returnValue = [...returnValue, ...source];
  303. } else if (isObject$1(source)) {
  304. for (let [key, value] of Object.entries(source)) {
  305. if (isObject$1(value) && key in returnValue) {
  306. value = deepMerge(returnValue[key], value);
  307. }
  308. returnValue = { ...returnValue, [key]: value };
  309. }
  310. if (isObject$1(source.headers)) {
  311. headers = mergeHeaders(headers, source.headers);
  312. returnValue.headers = headers;
  313. }
  314. }
  315. }
  316. return returnValue;
  317. };
  318. const supportsRequestStreams = (() => {
  319. let duplexAccessed = false;
  320. let hasContentType = false;
  321. const supportsReadableStream = typeof globalThis.ReadableStream === "function";
  322. if (supportsReadableStream) {
  323. hasContentType = new globalThis.Request("https://a.com", {
  324. body: new globalThis.ReadableStream(),
  325. method: "POST",
  326. // @ts-expect-error - Types are outdated.
  327. get duplex() {
  328. duplexAccessed = true;
  329. return "half";
  330. }
  331. }).headers.has("Content-Type");
  332. }
  333. return duplexAccessed && !hasContentType;
  334. })();
  335. const supportsAbortController = typeof globalThis.AbortController === "function";
  336. const supportsResponseStreams = typeof globalThis.ReadableStream === "function";
  337. const supportsFormData = typeof globalThis.FormData === "function";
  338. const requestMethods = ["get", "post", "put", "patch", "head", "delete"];
  339. const responseTypes = {
  340. json: "application/json",
  341. text: "text/*",
  342. formData: "multipart/form-data",
  343. arrayBuffer: "*/*",
  344. blob: "*/*"
  345. };
  346. const maxSafeTimeout = 2147483647;
  347. const stop = Symbol("stop");
  348. const normalizeRequestMethod = (input) => requestMethods.includes(input) ? input.toUpperCase() : input;
  349. const retryMethods = ["get", "put", "head", "delete", "options", "trace"];
  350. const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
  351. const retryAfterStatusCodes = [413, 429, 503];
  352. const defaultRetryOptions = {
  353. limit: 2,
  354. methods: retryMethods,
  355. statusCodes: retryStatusCodes,
  356. afterStatusCodes: retryAfterStatusCodes,
  357. maxRetryAfter: Number.POSITIVE_INFINITY,
  358. backoffLimit: Number.POSITIVE_INFINITY
  359. };
  360. const normalizeRetryOptions = (retry = {}) => {
  361. if (typeof retry === "number") {
  362. return {
  363. ...defaultRetryOptions,
  364. limit: retry
  365. };
  366. }
  367. if (retry.methods && !Array.isArray(retry.methods)) {
  368. throw new Error("retry.methods must be an array");
  369. }
  370. if (retry.statusCodes && !Array.isArray(retry.statusCodes)) {
  371. throw new Error("retry.statusCodes must be an array");
  372. }
  373. return {
  374. ...defaultRetryOptions,
  375. ...retry,
  376. afterStatusCodes: retryAfterStatusCodes
  377. };
  378. };
  379. async function timeout(request, abortController, options) {
  380. return new Promise((resolve, reject) => {
  381. const timeoutId = setTimeout(() => {
  382. if (abortController) {
  383. abortController.abort();
  384. }
  385. reject(new TimeoutError(request));
  386. }, options.timeout);
  387. void options.fetch(request).then(resolve).catch(reject).then(() => {
  388. clearTimeout(timeoutId);
  389. });
  390. });
  391. }
  392. const isDomExceptionSupported = Boolean(globalThis.DOMException);
  393. function composeAbortError(signal) {
  394. if (isDomExceptionSupported) {
  395. return new DOMException((signal == null ? void 0 : signal.reason) ?? "The operation was aborted.", "AbortError");
  396. }
  397. const error = new Error((signal == null ? void 0 : signal.reason) ?? "The operation was aborted.");
  398. error.name = "AbortError";
  399. return error;
  400. }
  401. async function delay(ms, { signal }) {
  402. return new Promise((resolve, reject) => {
  403. if (signal) {
  404. if (signal.aborted) {
  405. reject(composeAbortError(signal));
  406. return;
  407. }
  408. signal.addEventListener("abort", handleAbort, { once: true });
  409. }
  410. function handleAbort() {
  411. reject(composeAbortError(signal));
  412. clearTimeout(timeoutId);
  413. }
  414. const timeoutId = setTimeout(() => {
  415. signal == null ? void 0 : signal.removeEventListener("abort", handleAbort);
  416. resolve();
  417. }, ms);
  418. });
  419. }
  420. class Ky {
  421. // eslint-disable-next-line @typescript-eslint/promise-function-async
  422. static create(input, options) {
  423. const ky2 = new Ky(input, options);
  424. const fn = async () => {
  425. if (ky2._options.timeout > maxSafeTimeout) {
  426. throw new RangeError(`The \`timeout\` option cannot be greater than ${maxSafeTimeout}`);
  427. }
  428. await Promise.resolve();
  429. let response = await ky2._fetch();
  430. for (const hook of ky2._options.hooks.afterResponse) {
  431. const modifiedResponse = await hook(ky2.request, ky2._options, ky2._decorateResponse(response.clone()));
  432. if (modifiedResponse instanceof globalThis.Response) {
  433. response = modifiedResponse;
  434. }
  435. }
  436. ky2._decorateResponse(response);
  437. if (!response.ok && ky2._options.throwHttpErrors) {
  438. let error = new HTTPError(response, ky2.request, ky2._options);
  439. for (const hook of ky2._options.hooks.beforeError) {
  440. error = await hook(error);
  441. }
  442. throw error;
  443. }
  444. if (ky2._options.onDownloadProgress) {
  445. if (typeof ky2._options.onDownloadProgress !== "function") {
  446. throw new TypeError("The `onDownloadProgress` option must be a function");
  447. }
  448. if (!supportsResponseStreams) {
  449. throw new Error("Streams are not supported in your environment. `ReadableStream` is missing.");
  450. }
  451. return ky2._stream(response.clone(), ky2._options.onDownloadProgress);
  452. }
  453. return response;
  454. };
  455. const isRetriableMethod = ky2._options.retry.methods.includes(ky2.request.method.toLowerCase());
  456. const result = isRetriableMethod ? ky2._retry(fn) : fn();
  457. for (const [type, mimeType] of Object.entries(responseTypes)) {
  458. result[type] = async () => {
  459. ky2.request.headers.set("accept", ky2.request.headers.get("accept") || mimeType);
  460. const awaitedResult = await result;
  461. const response = awaitedResult.clone();
  462. if (type === "json") {
  463. if (response.status === 204) {
  464. return "";
  465. }
  466. const arrayBuffer = await response.clone().arrayBuffer();
  467. const responseSize = arrayBuffer.byteLength;
  468. if (responseSize === 0) {
  469. return "";
  470. }
  471. if (options.parseJson) {
  472. return options.parseJson(await response.text());
  473. }
  474. }
  475. return response[type]();
  476. };
  477. }
  478. return result;
  479. }
  480. // eslint-disable-next-line complexity
  481. constructor(input, options = {}) {
  482. Object.defineProperty(this, "request", {
  483. enumerable: true,
  484. configurable: true,
  485. writable: true,
  486. value: void 0
  487. });
  488. Object.defineProperty(this, "abortController", {
  489. enumerable: true,
  490. configurable: true,
  491. writable: true,
  492. value: void 0
  493. });
  494. Object.defineProperty(this, "_retryCount", {
  495. enumerable: true,
  496. configurable: true,
  497. writable: true,
  498. value: 0
  499. });
  500. Object.defineProperty(this, "_input", {
  501. enumerable: true,
  502. configurable: true,
  503. writable: true,
  504. value: void 0
  505. });
  506. Object.defineProperty(this, "_options", {
  507. enumerable: true,
  508. configurable: true,
  509. writable: true,
  510. value: void 0
  511. });
  512. this._input = input;
  513. this._options = {
  514. // TODO: credentials can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
  515. credentials: this._input.credentials || "same-origin",
  516. ...options,
  517. headers: mergeHeaders(this._input.headers, options.headers),
  518. hooks: deepMerge({
  519. beforeRequest: [],
  520. beforeRetry: [],
  521. beforeError: [],
  522. afterResponse: []
  523. }, options.hooks),
  524. method: normalizeRequestMethod(options.method ?? this._input.method),
  525. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
  526. prefixUrl: String(options.prefixUrl || ""),
  527. retry: normalizeRetryOptions(options.retry),
  528. throwHttpErrors: options.throwHttpErrors !== false,
  529. timeout: typeof options.timeout === "undefined" ? 1e4 : options.timeout,
  530. fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
  531. };
  532. if (typeof this._input !== "string" && !(this._input instanceof URL || this._input instanceof globalThis.Request)) {
  533. throw new TypeError("`input` must be a string, URL, or Request");
  534. }
  535. if (this._options.prefixUrl && typeof this._input === "string") {
  536. if (this._input.startsWith("/")) {
  537. throw new Error("`input` must not begin with a slash when using `prefixUrl`");
  538. }
  539. if (!this._options.prefixUrl.endsWith("/")) {
  540. this._options.prefixUrl += "/";
  541. }
  542. this._input = this._options.prefixUrl + this._input;
  543. }
  544. if (supportsAbortController) {
  545. this.abortController = new globalThis.AbortController();
  546. if (this._options.signal) {
  547. const originalSignal = this._options.signal;
  548. this._options.signal.addEventListener("abort", () => {
  549. this.abortController.abort(originalSignal.reason);
  550. });
  551. }
  552. this._options.signal = this.abortController.signal;
  553. }
  554. if (supportsRequestStreams) {
  555. this._options.duplex = "half";
  556. }
  557. this.request = new globalThis.Request(this._input, this._options);
  558. if (this._options.searchParams) {
  559. const textSearchParams = typeof this._options.searchParams === "string" ? this._options.searchParams.replace(/^\?/, "") : new URLSearchParams(this._options.searchParams).toString();
  560. const searchParams = "?" + textSearchParams;
  561. const url = this.request.url.replace(/(?:\?.*?)?(?=#|$)/, searchParams);
  562. if ((supportsFormData && this._options.body instanceof globalThis.FormData || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers["content-type"])) {
  563. this.request.headers.delete("content-type");
  564. }
  565. this.request = new globalThis.Request(new globalThis.Request(url, { ...this.request }), this._options);
  566. }
  567. if (this._options.json !== void 0) {
  568. this._options.body = JSON.stringify(this._options.json);
  569. this.request.headers.set("content-type", this._options.headers.get("content-type") ?? "application/json");
  570. this.request = new globalThis.Request(this.request, { body: this._options.body });
  571. }
  572. }
  573. _calculateRetryDelay(error) {
  574. this._retryCount++;
  575. if (this._retryCount < this._options.retry.limit && !(error instanceof TimeoutError)) {
  576. if (error instanceof HTTPError) {
  577. if (!this._options.retry.statusCodes.includes(error.response.status)) {
  578. return 0;
  579. }
  580. const retryAfter = error.response.headers.get("Retry-After");
  581. if (retryAfter && this._options.retry.afterStatusCodes.includes(error.response.status)) {
  582. let after = Number(retryAfter);
  583. if (Number.isNaN(after)) {
  584. after = Date.parse(retryAfter) - Date.now();
  585. } else {
  586. after *= 1e3;
  587. }
  588. if (typeof this._options.retry.maxRetryAfter !== "undefined" && after > this._options.retry.maxRetryAfter) {
  589. return 0;
  590. }
  591. return after;
  592. }
  593. if (error.response.status === 413) {
  594. return 0;
  595. }
  596. }
  597. const BACKOFF_FACTOR = 0.3;
  598. return Math.min(this._options.retry.backoffLimit, BACKOFF_FACTOR * 2 ** (this._retryCount - 1) * 1e3);
  599. }
  600. return 0;
  601. }
  602. _decorateResponse(response) {
  603. if (this._options.parseJson) {
  604. response.json = async () => this._options.parseJson(await response.text());
  605. }
  606. return response;
  607. }
  608. async _retry(fn) {
  609. try {
  610. return await fn();
  611. } catch (error) {
  612. const ms = Math.min(this._calculateRetryDelay(error), maxSafeTimeout);
  613. if (ms !== 0 && this._retryCount > 0) {
  614. await delay(ms, { signal: this._options.signal });
  615. for (const hook of this._options.hooks.beforeRetry) {
  616. const hookResult = await hook({
  617. request: this.request,
  618. options: this._options,
  619. error,
  620. retryCount: this._retryCount
  621. });
  622. if (hookResult === stop) {
  623. return;
  624. }
  625. }
  626. return this._retry(fn);
  627. }
  628. throw error;
  629. }
  630. }
  631. async _fetch() {
  632. for (const hook of this._options.hooks.beforeRequest) {
  633. const result = await hook(this.request, this._options);
  634. if (result instanceof Request) {
  635. this.request = result;
  636. break;
  637. }
  638. if (result instanceof Response) {
  639. return result;
  640. }
  641. }
  642. if (this._options.timeout === false) {
  643. return this._options.fetch(this.request.clone());
  644. }
  645. return timeout(this.request.clone(), this.abortController, this._options);
  646. }
  647. /* istanbul ignore next */
  648. _stream(response, onDownloadProgress) {
  649. const totalBytes = Number(response.headers.get("content-length")) || 0;
  650. let transferredBytes = 0;
  651. if (response.status === 204) {
  652. if (onDownloadProgress) {
  653. onDownloadProgress({ percent: 1, totalBytes, transferredBytes }, new Uint8Array());
  654. }
  655. return new globalThis.Response(null, {
  656. status: response.status,
  657. statusText: response.statusText,
  658. headers: response.headers
  659. });
  660. }
  661. return new globalThis.Response(new globalThis.ReadableStream({
  662. async start(controller) {
  663. const reader = response.body.getReader();
  664. if (onDownloadProgress) {
  665. onDownloadProgress({ percent: 0, transferredBytes: 0, totalBytes }, new Uint8Array());
  666. }
  667. async function read() {
  668. const { done, value } = await reader.read();
  669. if (done) {
  670. controller.close();
  671. return;
  672. }
  673. if (onDownloadProgress) {
  674. transferredBytes += value.byteLength;
  675. const percent = totalBytes === 0 ? 0 : transferredBytes / totalBytes;
  676. onDownloadProgress({ percent, transferredBytes, totalBytes }, value);
  677. }
  678. controller.enqueue(value);
  679. await read();
  680. }
  681. await read();
  682. }
  683. }), {
  684. status: response.status,
  685. statusText: response.statusText,
  686. headers: response.headers
  687. });
  688. }
  689. }
  690. /*! MIT License © Sindre Sorhus */
  691. const createInstance = (defaults) => {
  692. const ky2 = (input, options) => Ky.create(input, validateAndMerge(defaults, options));
  693. for (const method of requestMethods) {
  694. ky2[method] = (input, options) => Ky.create(input, validateAndMerge(defaults, options, { method }));
  695. }
  696. ky2.create = (newDefaults) => createInstance(validateAndMerge(newDefaults));
  697. ky2.extend = (newDefaults) => createInstance(validateAndMerge(defaults, newDefaults));
  698. ky2.stop = stop;
  699. return ky2;
  700. };
  701. const ky = createInstance();
  702. const ky$1 = ky;
  703. const kyfetch = ky$1.extend({ fetch: GM_fetch });
  704. async function fetchMagnetLink() {
  705. var _a, _b, _c;
  706. const query = () => document.querySelector(
  707. `.block-body article.message .message-body a[href*="obdown.com"][href*=".torrent"]`
  708. );
  709. const timeout2 = performance.now() + 5e3;
  710. while (!query() && performance.now() < timeout2) {
  711. await delayExports(500);
  712. }
  713. const a2 = query();
  714. if (!a2)
  715. return;
  716. const torrentPageUrl = a2.getAttribute("href");
  717. if (!torrentPageUrl)
  718. return;
  719. console.log("[tanhuazu-helper]: torrent download page %s", torrentPageUrl);
  720. const html = await kyfetch.get(torrentPageUrl, {
  721. retry: 5
  722. }).text();
  723. const p2 = new DOMParser();
  724. const doc = p2.parseFromString(html, "text/html");
  725. const magnetSpan = Array.from(
  726. doc.querySelectorAll("span.text-secondary")
  727. ).filter((span) => {
  728. var _a2;
  729. return ((_a2 = span.textContent) == null ? void 0 : _a2.trim()) === "MAGENT";
  730. })[0];
  731. 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");
  732. console.log("[tanhuazu-helper]: magnet link %s", magnet);
  733. if (!magnet)
  734. return;
  735. const firstMessage = document.querySelector(
  736. ".block-body article.message"
  737. );
  738. firstMessage.style.position = "relative";
  739. const btnShare = (_c = firstMessage.querySelector('a[aria-label="分享"]')) == null ? void 0 : _c.parentElement;
  740. const btnDl = document.createElement("li");
  741. btnDl.innerHTML = `
  742. <a href="${magnet}" class="message-attribution-gadget" title="磁力链接">
  743. <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>
  744. </a>
  745. `;
  746. btnShare == null ? void 0 : btnShare.insertAdjacentElement("afterend", btnDl);
  747. btnShare == null ? void 0 : btnShare.remove();
  748. const createBtn = () => {
  749. const btn = document.createElement("a");
  750. btn.href = magnet;
  751. btn.innerHTML = `
  752. <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>
  753. <span>下载</span>
  754. `;
  755. btn.className = "tanhuazu-download-btn";
  756. return btn;
  757. };
  758. const topBtn = createBtn();
  759. firstMessage.appendChild(topBtn);
  760. const bottomBtn = createBtn();
  761. bottomBtn.style.top = "unset";
  762. bottomBtn.style.bottom = "0";
  763. firstMessage.appendChild(bottomBtn);
  764. }
  765. var jsxRuntimeExports = {};
  766. var jsxRuntime = {
  767. get exports() {
  768. return jsxRuntimeExports;
  769. },
  770. set exports(v) {
  771. jsxRuntimeExports = v;
  772. }
  773. };
  774. var reactJsxRuntime_production_min = {};
  775. /**
  776. * @license React
  777. * react-jsx-runtime.production.min.js
  778. *
  779. * Copyright (c) Facebook, Inc. and its affiliates.
  780. *
  781. * This source code is licensed under the MIT license found in the
  782. * LICENSE file in the root directory of this source tree.
  783. */
  784. 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 };
  785. function q$1(c2, a2, g) {
  786. var b, d = {}, e2 = null, h2 = null;
  787. void 0 !== g && (e2 = "" + g);
  788. void 0 !== a2.key && (e2 = "" + a2.key);
  789. void 0 !== a2.ref && (h2 = a2.ref);
  790. for (b in a2)
  791. m$2.call(a2, b) && !p$2.hasOwnProperty(b) && (d[b] = a2[b]);
  792. if (c2 && c2.defaultProps)
  793. for (b in a2 = c2.defaultProps, a2)
  794. void 0 === d[b] && (d[b] = a2[b]);
  795. return { $$typeof: k$1, type: c2, key: e2, ref: h2, props: d, _owner: n$2.current };
  796. }
  797. reactJsxRuntime_production_min.Fragment = l$2;
  798. reactJsxRuntime_production_min.jsx = q$1;
  799. reactJsxRuntime_production_min.jsxs = q$1;
  800. (function(module) {
  801. {
  802. module.exports = reactJsxRuntime_production_min;
  803. }
  804. })(jsxRuntime);
  805. const jsx = jsxRuntimeExports.jsx;
  806. var createRoot;
  807. var m$1 = require$$0$1;
  808. {
  809. createRoot = m$1.createRoot;
  810. m$1.hydrateRoot;
  811. }
  812. const e$1 = Symbol(), t$1 = Symbol(), r$1 = "a", n$1 = "w";
  813. let o = (e2, t2) => new Proxy(e2, t2);
  814. 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) => {
  815. if (Array.isArray(e2))
  816. return Array.from(e2);
  817. const t2 = Object.getOwnPropertyDescriptors(e2);
  818. return Object.values(t2).forEach((e3) => {
  819. e3.configurable = true;
  820. }), Object.create(s(e2), t2);
  821. }, u$1 = (e2) => e2[t$1] || e2, a = (s2, c2, f2, p2) => {
  822. if (!l$1(s2))
  823. return s2;
  824. let g = p2 && p2.get(s2);
  825. if (!g) {
  826. const e2 = u$1(s2);
  827. g = ((e3) => Object.values(Object.getOwnPropertyDescriptors(e3)).some((e4) => !e4.configurable && !e4.writable))(e2) ? [e2, i(e2)] : [e2], null == p2 || p2.set(s2, g);
  828. }
  829. const [y2, h2] = g;
  830. let w2 = f2 && f2.get(y2);
  831. return w2 && w2[1].f === !!h2 || (w2 = ((o2, s3) => {
  832. const c3 = { f: s3 };
  833. let l2 = false;
  834. const f3 = (e2, t2) => {
  835. if (!l2) {
  836. let s4 = c3[r$1].get(o2);
  837. if (s4 || (s4 = {}, c3[r$1].set(o2, s4)), e2 === n$1)
  838. s4[n$1] = true;
  839. else {
  840. let r2 = s4[e2];
  841. r2 || (r2 = /* @__PURE__ */ new Set(), s4[e2] = r2), r2.add(t2);
  842. }
  843. }
  844. }, 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)) };
  845. return s3 && (i2.set = i2.deleteProperty = () => false), [i2, c3];
  846. })(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;
  847. }, p$1 = (e2, t2, r2, o2) => {
  848. if (Object.is(e2, t2))
  849. return false;
  850. if (!f(e2) || !f(t2))
  851. return true;
  852. const s2 = r2.get(u$1(e2));
  853. if (!s2)
  854. return true;
  855. if (o2) {
  856. const r3 = o2.get(e2);
  857. if (r3 && r3.n === t2)
  858. return r3.g;
  859. o2.set(e2, { n: t2, g: false });
  860. }
  861. let c2 = null;
  862. try {
  863. for (const r3 of s2.h || [])
  864. if (c2 = Reflect.has(e2, r3) !== Reflect.has(t2, r3), c2)
  865. return c2;
  866. if (true === s2[n$1]) {
  867. if (c2 = ((e3, t3) => {
  868. const r3 = Reflect.ownKeys(e3), n2 = Reflect.ownKeys(t3);
  869. return r3.length !== n2.length || r3.some((e4, t4) => e4 !== n2[t4]);
  870. })(e2, t2), c2)
  871. return c2;
  872. } else
  873. for (const r3 of s2.o || [])
  874. if (c2 = !!Reflect.getOwnPropertyDescriptor(e2, r3) != !!Reflect.getOwnPropertyDescriptor(t2, r3), c2)
  875. return c2;
  876. for (const n2 of s2.k || [])
  877. if (c2 = p$1(e2[n2], t2[n2], r2, o2), c2)
  878. return c2;
  879. return null === c2 && (c2 = true), c2;
  880. } finally {
  881. o2 && o2.set(e2, { n: t2, g: c2 });
  882. }
  883. }, y = (e2) => l$1(e2) && e2[t$1] || null, h$1 = (e2, t2 = true) => {
  884. c.set(e2, t2);
  885. }, w = (e2, t2, r2) => {
  886. const o2 = [], s2 = /* @__PURE__ */ new WeakSet(), c2 = (e3, l2) => {
  887. if (s2.has(e3))
  888. return;
  889. f(e3) && s2.add(e3);
  890. const i2 = f(e3) && t2.get(u$1(e3));
  891. if (i2) {
  892. var a2, p2;
  893. if (null == (a2 = i2.h) || a2.forEach((e4) => {
  894. const t3 = `:has(${String(e4)})`;
  895. o2.push(l2 ? [...l2, t3] : [t3]);
  896. }), true === i2[n$1]) {
  897. const e4 = ":ownKeys";
  898. o2.push(l2 ? [...l2, e4] : [e4]);
  899. } else {
  900. var g;
  901. null == (g = i2.o) || g.forEach((e4) => {
  902. const t3 = `:hasOwn(${String(e4)})`;
  903. o2.push(l2 ? [...l2, t3] : [t3]);
  904. });
  905. }
  906. null == (p2 = i2.k) || p2.forEach((t3) => {
  907. r2 && !("value" in (Object.getOwnPropertyDescriptor(e3, t3) || {})) || c2(e3[t3], l2 ? [...l2, t3] : [t3]);
  908. });
  909. } else
  910. l2 && o2.push(l2);
  911. };
  912. return c2(e2), o2;
  913. };
  914. const isObject = (x) => typeof x === "object" && x !== null;
  915. const proxyStateMap = /* @__PURE__ */ new WeakMap();
  916. const refSet = /* @__PURE__ */ new WeakSet();
  917. 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) => {
  918. switch (promise.status) {
  919. case "fulfilled":
  920. return promise.value;
  921. case "rejected":
  922. throw promise.reason;
  923. default:
  924. throw promise;
  925. }
  926. }, snapCache = /* @__PURE__ */ new WeakMap(), createSnapshot = (target, version, handlePromise = defaultHandlePromise) => {
  927. const cache = snapCache.get(target);
  928. if ((cache == null ? void 0 : cache[0]) === version) {
  929. return cache[1];
  930. }
  931. const snap = Array.isArray(target) ? [] : Object.create(Object.getPrototypeOf(target));
  932. h$1(snap, true);
  933. snapCache.set(target, [version, snap]);
  934. Reflect.ownKeys(target).forEach((key) => {
  935. if (Object.getOwnPropertyDescriptor(snap, key)) {
  936. return;
  937. }
  938. const value = Reflect.get(target, key);
  939. const desc = {
  940. value,
  941. enumerable: true,
  942. // This is intentional to avoid copying with proxy-compare.
  943. // It's still non-writable, so it avoids assigning a value.
  944. configurable: true
  945. };
  946. if (refSet.has(value)) {
  947. h$1(value, false);
  948. } else if (value instanceof Promise) {
  949. delete desc.value;
  950. desc.get = () => handlePromise(value);
  951. } else if (proxyStateMap.has(value)) {
  952. const [target2, ensureVersion] = proxyStateMap.get(
  953. value
  954. );
  955. desc.value = createSnapshot(
  956. target2,
  957. ensureVersion(),
  958. handlePromise
  959. );
  960. }
  961. Object.defineProperty(snap, key, desc);
  962. });
  963. return snap;
  964. }, proxyCache = /* @__PURE__ */ new WeakMap(), versionHolder = [1, 1], proxyFunction = (initialObject) => {
  965. if (!isObject(initialObject)) {
  966. throw new Error("object required");
  967. }
  968. const found = proxyCache.get(initialObject);
  969. if (found) {
  970. return found;
  971. }
  972. let version = versionHolder[0];
  973. const listeners = /* @__PURE__ */ new Set();
  974. const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => {
  975. if (version !== nextVersion) {
  976. version = nextVersion;
  977. listeners.forEach((listener) => listener(op, nextVersion));
  978. }
  979. };
  980. let checkVersion = versionHolder[1];
  981. const ensureVersion = (nextCheckVersion = ++versionHolder[1]) => {
  982. if (checkVersion !== nextCheckVersion && !listeners.size) {
  983. checkVersion = nextCheckVersion;
  984. propProxyStates.forEach(([propProxyState]) => {
  985. const propVersion = propProxyState[1](nextCheckVersion);
  986. if (propVersion > version) {
  987. version = propVersion;
  988. }
  989. });
  990. }
  991. return version;
  992. };
  993. const createPropListener = (prop) => (op, nextVersion) => {
  994. const newOp = [...op];
  995. newOp[1] = [prop, ...newOp[1]];
  996. notifyUpdate(newOp, nextVersion);
  997. };
  998. const propProxyStates = /* @__PURE__ */ new Map();
  999. const addPropListener = (prop, propProxyState) => {
  1000. if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && propProxyStates.has(prop)) {
  1001. throw new Error("prop listener already exists");
  1002. }
  1003. if (listeners.size) {
  1004. const remove = propProxyState[3](createPropListener(prop));
  1005. propProxyStates.set(prop, [propProxyState, remove]);
  1006. } else {
  1007. propProxyStates.set(prop, [propProxyState]);
  1008. }
  1009. };
  1010. const removePropListener = (prop) => {
  1011. var _a;
  1012. const entry = propProxyStates.get(prop);
  1013. if (entry) {
  1014. propProxyStates.delete(prop);
  1015. (_a = entry[1]) == null ? void 0 : _a.call(entry);
  1016. }
  1017. };
  1018. const addListener = (listener) => {
  1019. listeners.add(listener);
  1020. if (listeners.size === 1) {
  1021. propProxyStates.forEach(([propProxyState, prevRemove], prop) => {
  1022. if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && prevRemove) {
  1023. throw new Error("remove already exists");
  1024. }
  1025. const remove = propProxyState[3](createPropListener(prop));
  1026. propProxyStates.set(prop, [propProxyState, remove]);
  1027. });
  1028. }
  1029. const removeListener = () => {
  1030. listeners.delete(listener);
  1031. if (listeners.size === 0) {
  1032. propProxyStates.forEach(([propProxyState, remove], prop) => {
  1033. if (remove) {
  1034. remove();
  1035. propProxyStates.set(prop, [propProxyState]);
  1036. }
  1037. });
  1038. }
  1039. };
  1040. return removeListener;
  1041. };
  1042. const baseObject = Array.isArray(initialObject) ? [] : Object.create(Object.getPrototypeOf(initialObject));
  1043. const handler = {
  1044. deleteProperty(target, prop) {
  1045. const prevValue = Reflect.get(target, prop);
  1046. removePropListener(prop);
  1047. const deleted = Reflect.deleteProperty(target, prop);
  1048. if (deleted) {
  1049. notifyUpdate(["delete", [prop], prevValue]);
  1050. }
  1051. return deleted;
  1052. },
  1053. set(target, prop, value, receiver) {
  1054. const hasPrevValue = Reflect.has(target, prop);
  1055. const prevValue = Reflect.get(target, prop, receiver);
  1056. if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) {
  1057. return true;
  1058. }
  1059. removePropListener(prop);
  1060. if (isObject(value)) {
  1061. value = y(value) || value;
  1062. }
  1063. let nextValue = value;
  1064. if (value instanceof Promise) {
  1065. value.then((v) => {
  1066. value.status = "fulfilled";
  1067. value.value = v;
  1068. notifyUpdate(["resolve", [prop], v]);
  1069. }).catch((e2) => {
  1070. value.status = "rejected";
  1071. value.reason = e2;
  1072. notifyUpdate(["reject", [prop], e2]);
  1073. });
  1074. } else {
  1075. if (!proxyStateMap.has(value) && canProxy(value)) {
  1076. nextValue = proxyFunction(value);
  1077. }
  1078. const childProxyState = !refSet.has(nextValue) && proxyStateMap.get(nextValue);
  1079. if (childProxyState) {
  1080. addPropListener(prop, childProxyState);
  1081. }
  1082. }
  1083. Reflect.set(target, prop, nextValue, receiver);
  1084. notifyUpdate(["set", [prop], value, prevValue]);
  1085. return true;
  1086. }
  1087. };
  1088. const proxyObject = newProxy(baseObject, handler);
  1089. proxyCache.set(initialObject, proxyObject);
  1090. const proxyState = [
  1091. baseObject,
  1092. ensureVersion,
  1093. createSnapshot,
  1094. addListener
  1095. ];
  1096. proxyStateMap.set(proxyObject, proxyState);
  1097. Reflect.ownKeys(initialObject).forEach((key) => {
  1098. const desc = Object.getOwnPropertyDescriptor(
  1099. initialObject,
  1100. key
  1101. );
  1102. if ("value" in desc) {
  1103. proxyObject[key] = initialObject[key];
  1104. delete desc.value;
  1105. delete desc.writable;
  1106. }
  1107. Object.defineProperty(baseObject, key, desc);
  1108. });
  1109. return proxyObject;
  1110. }) => [
  1111. // public functions
  1112. proxyFunction,
  1113. // shared state
  1114. proxyStateMap,
  1115. refSet,
  1116. // internal things
  1117. objectIs,
  1118. newProxy,
  1119. canProxy,
  1120. defaultHandlePromise,
  1121. snapCache,
  1122. createSnapshot,
  1123. proxyCache,
  1124. versionHolder
  1125. ];
  1126. const [defaultProxyFunction] = buildProxyFunction();
  1127. function proxy(initialObject = {}) {
  1128. return defaultProxyFunction(initialObject);
  1129. }
  1130. function subscribe(proxyObject, callback, notifyInSync) {
  1131. const proxyState = proxyStateMap.get(proxyObject);
  1132. if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && !proxyState) {
  1133. console.warn("Please use proxy object");
  1134. }
  1135. let promise;
  1136. const ops = [];
  1137. const addListener = proxyState[3];
  1138. let isListenerActive = false;
  1139. const listener = (op) => {
  1140. ops.push(op);
  1141. if (notifyInSync) {
  1142. callback(ops.splice(0));
  1143. return;
  1144. }
  1145. if (!promise) {
  1146. promise = Promise.resolve().then(() => {
  1147. promise = void 0;
  1148. if (isListenerActive) {
  1149. callback(ops.splice(0));
  1150. }
  1151. });
  1152. }
  1153. };
  1154. const removeListener = addListener(listener);
  1155. isListenerActive = true;
  1156. return () => {
  1157. isListenerActive = false;
  1158. removeListener();
  1159. };
  1160. }
  1161. function snapshot(proxyObject, handlePromise) {
  1162. const proxyState = proxyStateMap.get(proxyObject);
  1163. if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production" && !proxyState) {
  1164. console.warn("Please use proxy object");
  1165. }
  1166. const [target, ensureVersion, createSnapshot] = proxyState;
  1167. return createSnapshot(target, ensureVersion(), handlePromise);
  1168. }
  1169. function ref(obj) {
  1170. refSet.add(obj);
  1171. return obj;
  1172. }
  1173. var shimExports = {};
  1174. var shim = {
  1175. get exports() {
  1176. return shimExports;
  1177. },
  1178. set exports(v) {
  1179. shimExports = v;
  1180. }
  1181. };
  1182. var useSyncExternalStoreShim_production_min = {};
  1183. /**
  1184. * @license React
  1185. * use-sync-external-store-shim.production.min.js
  1186. *
  1187. * Copyright (c) Facebook, Inc. and its affiliates.
  1188. *
  1189. * This source code is licensed under the MIT license found in the
  1190. * LICENSE file in the root directory of this source tree.
  1191. */
  1192. var e = require$$0;
  1193. function h(a2, b) {
  1194. return a2 === b && (0 !== a2 || 1 / a2 === 1 / b) || a2 !== a2 && b !== b;
  1195. }
  1196. var k = "function" === typeof Object.is ? Object.is : h, l = e.useState, m = e.useEffect, n = e.useLayoutEffect, p = e.useDebugValue;
  1197. function q(a2, b) {
  1198. var d = b(), f2 = l({ inst: { value: d, getSnapshot: b } }), c2 = f2[0].inst, g = f2[1];
  1199. n(function() {
  1200. c2.value = d;
  1201. c2.getSnapshot = b;
  1202. r(c2) && g({ inst: c2 });
  1203. }, [a2, d, b]);
  1204. m(function() {
  1205. r(c2) && g({ inst: c2 });
  1206. return a2(function() {
  1207. r(c2) && g({ inst: c2 });
  1208. });
  1209. }, [a2]);
  1210. p(d);
  1211. return d;
  1212. }
  1213. function r(a2) {
  1214. var b = a2.getSnapshot;
  1215. a2 = a2.value;
  1216. try {
  1217. var d = b();
  1218. return !k(a2, d);
  1219. } catch (f2) {
  1220. return true;
  1221. }
  1222. }
  1223. function t(a2, b) {
  1224. return b();
  1225. }
  1226. var u = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? t : q;
  1227. useSyncExternalStoreShim_production_min.useSyncExternalStore = void 0 !== e.useSyncExternalStore ? e.useSyncExternalStore : u;
  1228. (function(module) {
  1229. {
  1230. module.exports = useSyncExternalStoreShim_production_min;
  1231. }
  1232. })(shim);
  1233. const useSyncExternalStoreExports = /* @__PURE__ */ getDefaultExportFromCjs(shimExports);
  1234. const { use } = require$$0;
  1235. const { useSyncExternalStore } = useSyncExternalStoreExports;
  1236. const useAffectedDebugValue = (state2, affected) => {
  1237. const pathList = require$$0.useRef();
  1238. require$$0.useEffect(() => {
  1239. pathList.current = w(state2, affected, true);
  1240. });
  1241. require$$0.useDebugValue(pathList.current);
  1242. };
  1243. const targetCache = /* @__PURE__ */ new WeakMap();
  1244. function useSnapshot(proxyObject, options) {
  1245. const notifyInSync = options == null ? void 0 : options.sync;
  1246. const lastSnapshot = require$$0.useRef();
  1247. const lastAffected = require$$0.useRef();
  1248. let inRender = true;
  1249. const currSnapshot = useSyncExternalStore(
  1250. require$$0.useCallback(
  1251. (callback) => {
  1252. const unsub = subscribe(proxyObject, callback, notifyInSync);
  1253. callback();
  1254. return unsub;
  1255. },
  1256. [proxyObject, notifyInSync]
  1257. ),
  1258. () => {
  1259. const nextSnapshot = snapshot(proxyObject, use);
  1260. try {
  1261. if (!inRender && lastSnapshot.current && lastAffected.current && !p$1(
  1262. lastSnapshot.current,
  1263. nextSnapshot,
  1264. lastAffected.current,
  1265. /* @__PURE__ */ new WeakMap()
  1266. )) {
  1267. return lastSnapshot.current;
  1268. }
  1269. } catch (e2) {
  1270. }
  1271. return nextSnapshot;
  1272. },
  1273. () => snapshot(proxyObject, use)
  1274. );
  1275. inRender = false;
  1276. const currAffected = /* @__PURE__ */ new WeakMap();
  1277. require$$0.useEffect(() => {
  1278. lastSnapshot.current = currSnapshot;
  1279. lastAffected.current = currAffected;
  1280. });
  1281. if (({ "BASE_URL": "/", "MODE": "production", "DEV": false, "PROD": true, "SSR": false } && "production") !== "production") {
  1282. useAffectedDebugValue(currSnapshot, currAffected);
  1283. }
  1284. const proxyCache = require$$0.useMemo(() => /* @__PURE__ */ new WeakMap(), []);
  1285. return a(
  1286. currSnapshot,
  1287. currAffected,
  1288. proxyCache,
  1289. targetCache
  1290. );
  1291. }
  1292. const previewImgWrapper = "_preview-img-wrapper_1v8wn_1";
  1293. const styles = {
  1294. previewImgWrapper
  1295. };
  1296. function showPreviewImgWhenHover(container) {
  1297. if (!container)
  1298. return;
  1299. container.onmouseover = async (e2) => {
  1300. const src = e2.target;
  1301. if (src.tagName.toLowerCase() !== "a")
  1302. return;
  1303. const u2 = new URL(src.href, location.href);
  1304. if (!u2.pathname.startsWith("/threads/"))
  1305. return;
  1306. const threadUrl = u2.pathname.split("/").slice(0, 3).join("/") + "/";
  1307. logWithLabel("hover: %s", threadUrl);
  1308. state.a = ref(src);
  1309. state.threadUrl = threadUrl;
  1310. const imgs = await getPreviewImg(threadUrl);
  1311. logWithLabel("fetched imgs", imgs);
  1312. if (state.threadUrl === threadUrl) {
  1313. state.imgs = imgs;
  1314. }
  1315. if (!root) {
  1316. const div = document.createElement("div");
  1317. div.classList.add("preview-img-root");
  1318. document.body.appendChild(div);
  1319. root = createRoot(div);
  1320. root.render(/* @__PURE__ */ jsx(PreviewImg, {}));
  1321. }
  1322. };
  1323. container.onmouseout = function(e2) {
  1324. state.a = void 0;
  1325. state.threadUrl = "";
  1326. state.imgs = [];
  1327. };
  1328. }
  1329. async function getPreviewImg(threadUrl) {
  1330. const html = await ky$1.get(threadUrl, { cache: "force-cache" }).text();
  1331. const parser = new DOMParser();
  1332. const doc = parser.parseFromString(html, "text/html");
  1333. const srcs = Array.from(
  1334. doc.querySelectorAll(
  1335. ".block-body .message:first-child .bbImageWrapper img"
  1336. )
  1337. ).map((img) => img.src);
  1338. return srcs;
  1339. }
  1340. const state = proxy({
  1341. threadUrl: "",
  1342. a: void 0,
  1343. imgs: []
  1344. });
  1345. function PreviewImg() {
  1346. const { imgs, a: a2 } = useSnapshot(state);
  1347. const x = require$$0.useMemo(() => {
  1348. const rect = a2 == null ? void 0 : a2.getBoundingClientRect();
  1349. const x2 = ((rect == null ? void 0 : rect.right) || 0) + 50;
  1350. return x2;
  1351. }, [a2]);
  1352. if (!imgs.length)
  1353. return null;
  1354. return /* @__PURE__ */ jsx(
  1355. "div",
  1356. {
  1357. className: styles.previewImgWrapper,
  1358. style: {
  1359. position: "fixed",
  1360. left: x,
  1361. top: 10,
  1362. width: `calc(100vw - ${x}px - 20px)`,
  1363. height: "calc(100vh - 20px)",
  1364. overflow: "hidden"
  1365. },
  1366. children: imgs.slice(0, 1).map((src) => {
  1367. return /* @__PURE__ */ jsx("img", { src, alt: "" }, src);
  1368. })
  1369. }
  1370. );
  1371. }
  1372. let root;
  1373. function postListMain() {
  1374. const container = getThreadListContainer();
  1375. if (!container)
  1376. return;
  1377. container.onclick = (e2) => {
  1378. markLastClicked(e2);
  1379. fixPostLink(e2);
  1380. };
  1381. showPreviewImgWhenHover(container);
  1382. }
  1383. function isSearchResultPage() {
  1384. if (!/^\/search\/\d+\//.test(location.pathname))
  1385. return false;
  1386. const u2 = new URL(location.href);
  1387. if (u2.searchParams.get("searchform") === "1")
  1388. return false;
  1389. return true;
  1390. }
  1391. function getThreadListContainer() {
  1392. let el;
  1393. if (el = document.querySelector(
  1394. [
  1395. '.block[data-type="thread"]',
  1396. '.block[data-widget-key="whats_new_new_posts"]',
  1397. // /what's new
  1398. '.p-body-pageContent:has(> form[action^="/watched/threads"])'
  1399. // /watched/threads
  1400. ].join(",")
  1401. )) {
  1402. return el;
  1403. }
  1404. if (isSearchResultPage() && (el = document.querySelector(".p-body-pageContent"))) {
  1405. return el;
  1406. }
  1407. if (el = document.querySelector(".p-body-pageContent")) {
  1408. return el;
  1409. }
  1410. }
  1411. function fixPostLink(e2) {
  1412. const src = e2.target;
  1413. if (src.tagName.toLowerCase() !== "a")
  1414. return;
  1415. const u2 = new URL(src.href, location.href);
  1416. if (!u2.pathname.startsWith("/threads/"))
  1417. return;
  1418. if (u2.pathname.includes("unread")) {
  1419. e2.preventDefault();
  1420. const newLink = src.href.replace(/unread/, "");
  1421. GM_openInTab(newLink);
  1422. }
  1423. }
  1424. function markLastClicked(e2) {
  1425. var _a;
  1426. const lineSelector = ".structItem.structItem--thread, .block-row";
  1427. const cur = e2.target.closest(lineSelector);
  1428. cur == null ? void 0 : cur.classList.add("last-clicked");
  1429. (_a = e2.currentTarget) == null ? void 0 : _a.querySelectorAll(lineSelector).forEach((item) => {
  1430. item !== cur && item.classList.remove("last-clicked");
  1431. });
  1432. }
  1433. const style = "";
  1434. main();
  1435. function main() {
  1436. document.body.classList.add("tanhuazu");
  1437. const p2 = location.pathname;
  1438. if (p2.startsWith("/threads/")) {
  1439. handleReplyWait();
  1440. fetchMagnetLink();
  1441. return;
  1442. }
  1443. if (p2.startsWith("/forums/") || // 论坛
  1444. p2 === "/whats-new/" || // 最新消息
  1445. p2.startsWith("/whats-new/posts/") || // 新帖
  1446. p2.startsWith("/find-threads/") || // 查找主题(mine, 已回复, 未回复)
  1447. p2.startsWith("/watched/threads") || // 关注主题
  1448. isSearchResultPage() || // 搜索结果
  1449. p2.startsWith("/tags/")) {
  1450. return postListMain();
  1451. }
  1452. }
  1453. })(React, ReactDOM);