增加wtf顶部筛选布局|实现标签排除筛选|支持proxy参数自定义反代|增加简单的概要标签|增加瀑布流选中反馈与单击放大

This commit is contained in:
inliver233 committed 2026-02-25 02:13:06 +08:00
1 parent ef66583b9c
commit ec9d373ab8
10 files changed
+1130 -54

No files matched your search

+28 -1
View File
@@ -12,7 +12,11 @@ from app.core.runtime_settings import (
runtime_config_from_values,
set_runtime_setting,
)
from app.core.pximg_reverse_proxy import DEFAULT_PXIMG_MIRROR_HOST, normalize_pximg_mirror_host
from app.core.pximg_reverse_proxy import (
DEFAULT_PXIMG_MIRROR_HOST,
normalize_pximg_custom_mirror_host,
normalize_pximg_mirror_host,
)
router = APIRouter()
@@ -255,6 +259,7 @@ async def get_settings(
"image_proxy": {
"use_pixiv_cat": bool(runtime.image_proxy_use_pixiv_cat),
"pximg_mirror_host": str(runtime.image_proxy_pximg_mirror_host or DEFAULT_PXIMG_MIRROR_HOST),
"extra_pximg_mirror_hosts": list(getattr(runtime, "image_proxy_extra_pximg_mirror_hosts", []) or []),
},
"random": random_defaults,
"security": {"hide_origin_url_in_public_json": bool(runtime.hide_origin_url_in_public_json)},
@@ -371,6 +376,28 @@ async def update_settings(
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid image_proxy.pximg_mirror_host", status_code=400)
updates.append(("image_proxy.pximg_mirror_host", str(host)))
if "extra_pximg_mirror_hosts" in image_proxy:
raw = image_proxy.get("extra_pximg_mirror_hosts")
if raw is None:
updates.append(("image_proxy.extra_pximg_mirror_hosts", []))
else:
if not isinstance(raw, list):
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid image_proxy.extra_pximg_mirror_hosts", status_code=400)
candidates = _as_str_list(raw)
if len(candidates) > 200:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid image_proxy.extra_pximg_mirror_hosts", status_code=400)
normalized: list[str] = []
seen: set[str] = set()
for item in candidates:
host = normalize_pximg_custom_mirror_host(item)
if host is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Invalid image_proxy.extra_pximg_mirror_hosts", status_code=400)
if host in seen:
continue
seen.add(host)
normalized.append(host)
updates.append(("image_proxy.extra_pximg_mirror_hosts", normalized))
random = body.get("random")
if random is not None:
if not isinstance(random, dict):
+21 -1
View File
@@ -19,6 +19,7 @@ def _build_docs_html(*, base_url: str) -> str:
examples = {
"img_default": u("/random"),
"img_proxy_cat": u("/random?proxy=i-pixiv-cat"),
"img_pixiv_cat": u("/random?pixiv_cat=1"),
"img_pixiv_re": u("/random?pixiv_cat=1&pximg_mirror_host=re"),
"img_redirect": u("/random?redirect=1"),
@@ -303,7 +304,8 @@ def _build_docs_html(*, base_url: str) -> str:
<p><span class="kbd">/status</span> 为公开仪表盘:展示 API 状态、图库概览、/random 请求统计;<span class="kbd">/status.json</span> 为机器可读 JSON。</p>
<pre><code>{examples["status"]}
{examples["status_json"]}</code></pre>
<p><span class="kbd">/wtf</span> 为瀑布流:参数与 <span class="kbd">/random</span> 完全一致(例如 r18/标签/分辨率/热度等),页面内部会自动加上 <span class="kbd">adaptive=1</span> 与 <span class="kbd">redirect=1</span> 来做屏幕适配与稳定链接。</p>
<p><span class="kbd">/wtf</span> 为瀑布流:支持 <span class="kbd">/random</span> 的全部过滤参数(例如 r18/标签/分辨率/热度/排除标签等),默认会补上 <span class="kbd">adaptive=1</span> 以更适合不同屏幕。</p>
<p class="muted">/wtf 额外支持布局参数:<code>view=single|masonry|tiles</code>,以及 <code>wtf_mcols</code>/<code>wtf_mgap</code>/<code>wtf_tcols</code>/<code>wtf_tgap</code>/<code>wtf_tratio</code>(仅影响页面布局)。另有 <code>wtf_gender=girls|boys</code> 与概要标签 <code>@male</code>/<code>@female</code>(仅 /wtf 端展开,用于快速“只看女生/只看男生”)。</p>
<pre><code>{examples["wtf"]}
{examples["wtf_r18"]}</code></pre>
</section>
@@ -332,6 +334,15 @@ def _build_docs_html(*, base_url: str) -> str:
<span class="muted">quality 会先抽样 N 张候选再按热度/分辨率打分选图。</span>
</td>
</tr>
<tr>
<td><code>proxy</code></td>
<td>
指定本次请求的图片上游镜像(优先级最高)。会隐式开启第三方镜像(等价于 <code>pixiv_cat=1</code>),并覆盖地区自动选择。<br/>
内置值:<code>cat</code>/<code>re</code>/<code>nl</code>,也支持 <code>pixiv-cat</code>/<code>i-pixiv-cat</code> 等写法。<br/>
自定义镜像:可填写你自建镜像域名(需在管理端“自定义镜像白名单”允许)。<br/>
示例:<a href="{examples["img_proxy_cat"]}">{examples["img_proxy_cat"]}</a>
</td>
</tr>
<tr>
<td><code>pixiv_cat</code></td>
<td>
@@ -417,6 +428,15 @@ def _build_docs_html(*, base_url: str) -> str:
<p>示例:R18 + 插画 + 竖图 + 250万像素以上 + 收藏≥2 + 评论≥5 + 标签包含 loli(直接出图)。</p>
<pre><code>{examples["complex"]}</code></pre>
</section>
<section class="card" style="margin-top: 14px;">
<h2>6) 管理端导入(可选)</h2>
<p class="muted">管理端支持从文件批量导入图片链接:.txt(每行一个 URL)或 PixivBatchDownloader 导出的 .json。</p>
<ul>
<li><strong>.json</strong> 导入会自动提取图片链接,并尽可能填充已有元数据/标签;不依赖 refresh token 也能使用(无需额外触发补全)。</li>
<li><strong>.txt</strong> 导入仅包含 URL,推荐在有 refresh token 时启用导入后补全,以获得更完整的标签/尺寸/R18/AI 等信息。</li>
</ul>
</section>
</div>
</body>
</html>
+15 -4
View File
@@ -14,6 +14,7 @@ import sqlalchemy as sa
from app.core.pixiv_urls import ALLOWED_IMAGE_EXTS
from app.core.pximg_reverse_proxy import (
normalize_pximg_mirror_host,
normalize_pximg_proxy,
pick_pximg_mirror_host_for_request,
rewrite_pximg_to_mirror,
)
@@ -277,6 +278,7 @@ async def proxy_image(
background_tasks: BackgroundTasks,
pixiv_cat: int = 0,
pximg_mirror_host: str | None = None,
proxy: str | None = None,
):
ext = (ext or "").lower()
if ext not in ALLOWED_IMAGE_EXTS:
@@ -284,12 +286,12 @@ async def proxy_image(
if pixiv_cat not in {0, 1}:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pixiv_cat", status_code=400)
mirror_host_override: str | None = None
pximg_mirror_host_override: str | None = None
if pximg_mirror_host is not None:
raw = str(pximg_mirror_host or "").strip()
if raw:
mirror_host_override = normalize_pximg_mirror_host(raw)
if mirror_host_override is None:
pximg_mirror_host_override = normalize_pximg_mirror_host(raw)
if pximg_mirror_host_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pximg_mirror_host", status_code=400)
engine = request.app.state.engine
@@ -327,7 +329,16 @@ async def proxy_image(
needs_hydrate = tag_row is None
runtime = await load_runtime_config(engine)
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1
proxy_override: str | None = None
if proxy is not None:
raw = str(proxy or "").strip()
if raw:
proxy_override = normalize_pximg_proxy(raw, extra_hosts=runtime.image_proxy_extra_pximg_mirror_hosts)
if proxy_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported proxy", status_code=400)
mirror_host_override = proxy_override or pximg_mirror_host_override
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1 or proxy_override is not None
runtime_mirror_host = str(getattr(runtime, "image_proxy_pximg_mirror_host", "") or "").strip() or "i.pixiv.cat"
mirror_host = mirror_host_override or (
pick_pximg_mirror_host_for_request(headers=request.headers, fallback_host=runtime_mirror_host)
+31 -8
View File
@@ -7,6 +7,7 @@ from app.core.http_stream import stream_url
from app.core.pixiv_urls import ALLOWED_IMAGE_EXTS
from app.core.pximg_reverse_proxy import (
normalize_pximg_mirror_host,
normalize_pximg_proxy,
pick_pximg_mirror_host_for_request,
rewrite_pximg_to_mirror,
)
@@ -26,6 +27,7 @@ async def legacy_multi(
ext: str,
pixiv_cat: int = 0,
pximg_mirror_host: str | None = None,
proxy: str | None = None,
):
if int(illust_id) <= 0:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported illust_id", status_code=400)
@@ -38,12 +40,12 @@ async def legacy_multi(
if pixiv_cat not in {0, 1}:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pixiv_cat", status_code=400)
mirror_host_override: str | None = None
pximg_mirror_host_override: str | None = None
if pximg_mirror_host is not None:
raw = str(pximg_mirror_host or "").strip()
if raw:
mirror_host_override = normalize_pximg_mirror_host(raw)
if mirror_host_override is None:
pximg_mirror_host_override = normalize_pximg_mirror_host(raw)
if pximg_mirror_host_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pximg_mirror_host", status_code=400)
engine = request.app.state.engine
@@ -55,7 +57,17 @@ async def legacy_multi(
raise ApiError(code=ErrorCode.NOT_FOUND, message="Image not found", status_code=404)
runtime = await load_runtime_config(engine)
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1
proxy_override: str | None = None
if proxy is not None:
raw = str(proxy or "").strip()
if raw:
proxy_override = normalize_pximg_proxy(raw, extra_hosts=runtime.image_proxy_extra_pximg_mirror_hosts)
if proxy_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported proxy", status_code=400)
mirror_host_override = proxy_override or pximg_mirror_host_override
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1 or proxy_override is not None
runtime_mirror_host = str(getattr(runtime, "image_proxy_pximg_mirror_host", "") or "").strip() or "i.pixiv.cat"
mirror_host = mirror_host_override or (
pick_pximg_mirror_host_for_request(headers=request.headers, fallback_host=runtime_mirror_host)
@@ -91,6 +103,7 @@ async def legacy_single(
ext: str,
pixiv_cat: int = 0,
pximg_mirror_host: str | None = None,
proxy: str | None = None,
):
if int(illust_id) <= 0:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported illust_id", status_code=400)
@@ -101,12 +114,12 @@ async def legacy_single(
if pixiv_cat not in {0, 1}:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pixiv_cat", status_code=400)
mirror_host_override: str | None = None
pximg_mirror_host_override: str | None = None
if pximg_mirror_host is not None:
raw = str(pximg_mirror_host or "").strip()
if raw:
mirror_host_override = normalize_pximg_mirror_host(raw)
if mirror_host_override is None:
pximg_mirror_host_override = normalize_pximg_mirror_host(raw)
if pximg_mirror_host_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pximg_mirror_host", status_code=400)
engine = request.app.state.engine
@@ -118,7 +131,17 @@ async def legacy_single(
raise ApiError(code=ErrorCode.NOT_FOUND, message="Image not found", status_code=404)
runtime = await load_runtime_config(engine)
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1
proxy_override: str | None = None
if proxy is not None:
raw = str(proxy or "").strip()
if raw:
proxy_override = normalize_pximg_proxy(raw, extra_hosts=runtime.image_proxy_extra_pximg_mirror_hosts)
if proxy_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported proxy", status_code=400)
mirror_host_override = proxy_override or pximg_mirror_host_override
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1 or proxy_override is not None
runtime_mirror_host = str(getattr(runtime, "image_proxy_pximg_mirror_host", "") or "").strip() or "i.pixiv.cat"
mirror_host = mirror_host_override or (
pick_pximg_mirror_host_for_request(headers=request.headers, fallback_host=runtime_mirror_host)
+23 -8
View File
@@ -15,6 +15,7 @@ from app.core.http_stream import stream_url
from app.core.imgproxy import build_signed_processing_url, load_imgproxy_config_from_settings
from app.core.pximg_reverse_proxy import (
normalize_pximg_mirror_host,
normalize_pximg_proxy,
pick_pximg_mirror_host_for_request,
rewrite_pximg_to_mirror,
)
@@ -131,6 +132,7 @@ async def random_image(
adaptive: int = 0,
pixiv_cat: int = 0,
pximg_mirror_host: str | None = None,
proxy: str | None = None,
min_width: int = 0,
min_height: int = 0,
min_pixels: int = 0,
@@ -185,12 +187,12 @@ async def random_image(
if pixiv_cat not in {0, 1}:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pixiv_cat", status_code=400)
mirror_host_override: str | None = None
pximg_mirror_host_override: str | None = None
if pximg_mirror_host is not None:
raw = str(pximg_mirror_host or "").strip()
if raw:
mirror_host_override = normalize_pximg_mirror_host(raw)
if mirror_host_override is None:
pximg_mirror_host_override = normalize_pximg_mirror_host(raw)
if pximg_mirror_host_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported pximg_mirror_host", status_code=400)
layout_source = "orientation"
@@ -381,7 +383,17 @@ async def random_image(
engine = request.app.state.engine
Session = create_sessionmaker(engine)
runtime = await load_runtime_config(engine)
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1
proxy_override: str | None = None
if proxy is not None:
raw = str(proxy or "").strip()
if raw:
proxy_override = normalize_pximg_proxy(raw, extra_hosts=runtime.image_proxy_extra_pximg_mirror_hosts)
if proxy_override is None:
raise ApiError(code=ErrorCode.BAD_REQUEST, message="Unsupported proxy", status_code=400)
mirror_host_override = proxy_override or pximg_mirror_host_override
use_pixiv_cat = bool(runtime.image_proxy_use_pixiv_cat) or int(pixiv_cat) == 1 or proxy_override is not None
runtime_mirror_host = str(getattr(runtime, "image_proxy_pximg_mirror_host", "") or "").strip() or "i.pixiv.cat"
mirror_host = mirror_host_override or (
pick_pximg_mirror_host_for_request(headers=request.headers, fallback_host=runtime_mirror_host)
@@ -757,10 +769,13 @@ async def random_image(
if format == "image" and redirect == 1:
qp: list[tuple[str, str]] = []
if int(pixiv_cat) == 1:
qp.append(("pixiv_cat", "1"))
if mirror_host_override is not None:
qp.append(("pximg_mirror_host", str(mirror_host_override)))
if proxy_override is not None:
qp.append(("proxy", str(proxy_override)))
else:
if int(pixiv_cat) == 1:
qp.append(("pixiv_cat", "1"))
if pximg_mirror_host_override is not None:
qp.append(("pximg_mirror_host", str(pximg_mirror_host_override)))
qs = ("?" + "&".join([f"{k}={v}" for k, v in qp])) if qp else ""
resp = RedirectResponse(
url=f"/i/{image.id}.{image.ext}{qs}",
File diff suppressed because it is too large. Load diff
+89 -1
View File
@@ -1,6 +1,8 @@
from __future__ import annotations
from collections.abc import Mapping
import ipaddress
import re
from urllib.parse import urlparse, urlunparse
@@ -15,18 +17,30 @@ ALLOWED_PXIMG_MIRROR_HOSTS: tuple[str, ...] = (
_PXIMG_MIRROR_ALIASES: dict[str, str] = {
# pixiv.cat
"cat": "i.pixiv.cat",
"pixiv-cat": "i.pixiv.cat",
"pixiv.cat": "i.pixiv.cat",
"i-pixiv-cat": "i.pixiv.cat",
"i.pixiv.cat": "i.pixiv.cat",
# pixiv.re (often recommended for CN mainland)
"re": "i.pixiv.re",
"pixiv-re": "i.pixiv.re",
"pixiv.re": "i.pixiv.re",
"i-pixiv-re": "i.pixiv.re",
"i.pixiv.re": "i.pixiv.re",
# pixiv.nl (often recommended for CN mainland)
"nl": "i.pixiv.nl",
"pixiv-nl": "i.pixiv.nl",
"pixiv.nl": "i.pixiv.nl",
"i-pixiv-nl": "i.pixiv.nl",
"i.pixiv.nl": "i.pixiv.nl",
}
_CUSTOM_HOST_RE = re.compile(
r"^(?=.{1,253}$)"
r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)"
r"(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))+$"
)
_REQUEST_COUNTRY_HEADERS: tuple[str, ...] = (
# Cloudflare
@@ -133,6 +147,80 @@ def normalize_pximg_mirror_host(value: object) -> str | None:
return None
def normalize_pximg_custom_mirror_host(value: object) -> str | None:
"""
Normalize a *custom* pximg mirror host. This does NOT enforce an allowlist.
Intended to be used for admin-configured allowlists (trusted) or after an allowlist check
is performed at the request boundary.
"""
if value is None:
return None
if isinstance(value, bool):
return None
raw = str(value or "").strip().lower()
if not raw:
return None
if raw.startswith("http://") or raw.startswith("https://"):
try:
parsed = urlparse(raw)
except Exception:
return None
raw = (parsed.hostname or "").strip().lower()
if not raw:
return None
raw = raw.strip().strip(".")
if not raw:
return None
if raw in {"localhost"}:
return None
try:
ipaddress.ip_address(raw)
except Exception:
pass
else:
return None
if not _CUSTOM_HOST_RE.match(raw):
return None
return raw
def normalize_pximg_proxy(value: object, *, extra_hosts: list[str] | tuple[str, ...] | None) -> str | None:
"""
Normalize pximg mirror selection from a public `proxy=` query param.
- Built-in hosts/aliases are always allowed.
- Custom hosts are only allowed if present in `extra_hosts` allowlist.
"""
built_in = normalize_pximg_mirror_host(value)
if built_in is not None:
return built_in
candidate = normalize_pximg_custom_mirror_host(value)
if candidate is None:
return None
if not extra_hosts:
return None
allow: set[str] = set()
for item in extra_hosts:
norm = normalize_pximg_custom_mirror_host(item)
if norm:
allow.add(norm)
if candidate in allow:
return candidate
return None
def rewrite_pximg_to_mirror(url: str, *, mirror_host: str) -> str:
try:
parsed = urlparse(url)
@@ -142,7 +230,7 @@ def rewrite_pximg_to_mirror(url: str, *, mirror_host: str) -> str:
if not is_pximg_image_url(url):
return url
mirror = normalize_pximg_mirror_host(mirror_host) or ""
mirror = normalize_pximg_mirror_host(mirror_host) or normalize_pximg_custom_mirror_host(mirror_host) or ""
if not mirror:
return url
+18 -1
View File
@@ -8,7 +8,11 @@ from sqlalchemy.dialects.sqlite import insert as sqlite_insert
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncEngine
from app.core.pximg_reverse_proxy import DEFAULT_PXIMG_MIRROR_HOST, normalize_pximg_mirror_host
from app.core.pximg_reverse_proxy import (
DEFAULT_PXIMG_MIRROR_HOST,
normalize_pximg_custom_mirror_host,
normalize_pximg_mirror_host,
)
from app.core.logging import get_logger
from app.core.time import iso_utc_ms
from app.db.models.runtime_settings import RuntimeSetting
@@ -57,6 +61,7 @@ class RuntimeConfig:
proxy_default_pool_id: int | None
image_proxy_use_pixiv_cat: bool
image_proxy_pximg_mirror_host: str
image_proxy_extra_pximg_mirror_hosts: list[str]
random_defaults: dict[str, Any]
hide_origin_url_in_public_json: bool
rate_limit: dict[str, Any]
@@ -72,6 +77,7 @@ class RuntimeConfig:
proxy_default_pool_id=None,
image_proxy_use_pixiv_cat=False,
image_proxy_pximg_mirror_host=str(DEFAULT_PXIMG_MIRROR_HOST),
image_proxy_extra_pximg_mirror_hosts=[],
random_defaults={},
hide_origin_url_in_public_json=True,
rate_limit={},
@@ -140,6 +146,16 @@ def runtime_config_from_values(values: dict[str, Any]) -> RuntimeConfig:
mirror_host_raw = values.get("image_proxy.pximg_mirror_host")
mirror_host = normalize_pximg_mirror_host(mirror_host_raw)
extra_mirrors_raw = _as_str_list(values.get("image_proxy.extra_pximg_mirror_hosts"))
extra_mirrors: list[str] = []
seen_extra: set[str] = set()
for item in extra_mirrors_raw:
host = normalize_pximg_custom_mirror_host(item)
if not host or host in seen_extra:
continue
seen_extra.add(host)
extra_mirrors.append(host)
random_defaults_raw = values.get("random.defaults")
random_defaults = defaults.random_defaults
if isinstance(random_defaults_raw, dict):
@@ -164,6 +180,7 @@ def runtime_config_from_values(values: dict[str, Any]) -> RuntimeConfig:
if image_proxy_use_pixiv_cat is not None
else defaults.image_proxy_use_pixiv_cat,
image_proxy_pximg_mirror_host=str(mirror_host or defaults.image_proxy_pximg_mirror_host),
image_proxy_extra_pximg_mirror_hosts=extra_mirrors,
random_defaults=random_defaults,
hide_origin_url_in_public_json=hide_origin_url
if hide_origin_url is not None
+4 -3
View File
@@ -29,6 +29,7 @@ describe("SettingsPage", () => {
expect(body.settings.proxy.default_pool_id).toBe("");
expect(body.settings.image_proxy.use_pixiv_cat).toBe(false);
expect(body.settings.image_proxy.pximg_mirror_host).toBe("i.pixiv.cat");
expect(body.settings.image_proxy.extra_pximg_mirror_hosts).toEqual([]);
expect(body.settings.random.default_attempts).toBe(3);
expect(body.settings.random.default_r18_strict).toBe(true);
expect(body.settings.random.fail_cooldown_ms).toBe(600000);
@@ -37,7 +38,7 @@ describe("SettingsPage", () => {
expect(body.settings.security.hide_origin_url_in_public_json).toBe(true);
expect(body.settings.rate_limit.pixiv_hydrate_min_interval_ms).toBe(800);
expect(body.settings.rate_limit.pixiv_hydrate_jitter_ms).toBe(200);
return new Response(JSON.stringify({ ok: true, updated: 9, request_id: "req_save" }), {
return new Response(JSON.stringify({ ok: true, updated: 10, request_id: "req_save" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
@@ -54,7 +55,7 @@ describe("SettingsPage", () => {
allowlist_domains: ["pixiv.net"],
default_pool_id: "",
},
image_proxy: { use_pixiv_cat: false, pximg_mirror_host: "i.pixiv.cat" },
image_proxy: { use_pixiv_cat: false, pximg_mirror_host: "i.pixiv.cat", extra_pximg_mirror_hosts: [] },
random: { default_attempts: 3, default_r18_strict: true, fail_cooldown_ms: 600000, strategy: "quality", quality_samples: 5 },
security: { hide_origin_url_in_public_json: true },
rate_limit: {},
@@ -98,7 +99,7 @@ describe("SettingsPage", () => {
fireEvent.click(screen.getByRole("button", { name: /保存设置/ }));
expect(await screen.findByText("保存成功")).toBeInTheDocument();
expect(await screen.findByText(/更新条目数:\s*9/)).toBeInTheDocument();
expect(await screen.findByText(/更新条目数:\s*10/)).toBeInTheDocument();
expect(await screen.findByText(/请求ID:\s*req_save/)).toBeInTheDocument();
});
});
+10
View File
@@ -37,6 +37,7 @@ type SettingsFormValues = {
proxy_default_pool_id: number;
image_proxy_use_pixiv_cat: boolean;
image_proxy_pximg_mirror_host: "i.pixiv.cat" | "i.pixiv.re" | "i.pixiv.nl";
image_proxy_extra_pximg_mirror_hosts: string[];
random_default_attempts: number;
random_default_r18_strict: boolean;
random_fail_cooldown_ms: number;
@@ -147,6 +148,7 @@ export function SettingsPage() {
proxy_default_pool_id: asInt(proxy.default_pool_id, 0),
image_proxy_use_pixiv_cat: asBool(imageProxy.use_pixiv_cat, false),
image_proxy_pximg_mirror_host: asPximgMirrorHost(imageProxy.pximg_mirror_host, "i.pixiv.cat"),
image_proxy_extra_pximg_mirror_hosts: asStrList(imageProxy.extra_pximg_mirror_hosts),
random_default_attempts: asInt(random.default_attempts, 3),
random_default_r18_strict: asBool(random.default_r18_strict, true),
random_fail_cooldown_ms: asInt(random.fail_cooldown_ms, 600_000),
@@ -174,6 +176,7 @@ export function SettingsPage() {
image_proxy: {
use_pixiv_cat: values.image_proxy_use_pixiv_cat,
pximg_mirror_host: values.image_proxy_pximg_mirror_host,
extra_pximg_mirror_hosts: values.image_proxy_extra_pximg_mirror_hosts,
},
random: {
default_attempts: values.random_default_attempts,
@@ -312,6 +315,13 @@ export function SettingsPage() {
]}
/>
</Form.Item>
<Form.Item
label="自定义镜像白名单"
name="image_proxy_extra_pximg_mirror_hosts"
extra="用于公开接口的 proxy= 参数:仅允许这里配置的自定义域名被用作图片上游镜像(防止 SSRF)。示例:i.mirror.example.com"
>
<Select mode="tags" style={{ maxWidth: 520 }} tokenSeparators={[",", "\n", " "]} placeholder="例如:i.mirror.example.com" />
</Form.Item>
<Typography.Title level={5} style={{ marginTop: 12 }}>
随机接口设置