refactor: split modules and harden scraping workflows

This commit is contained in:
ShukeBta
2026-06-24 11:59:18 +08:00
parent efca3cbe69
commit 192f35d9fa
470 changed files with 47957 additions and 33839 deletions
+22 -436
View File
@@ -1,7 +1,11 @@
import type { Dispatch, FormEvent, SetStateAction } from "react";
import { RefreshCw, X } from "lucide-react";
import { X } from "lucide-react";
import type { SiteForm } from "./sitesPageModel";
import { SiteFormActions } from "./SiteFormActions";
import { SiteFormAdvancedOptions } from "./SiteFormAdvancedOptions";
import { SiteFormAuthFields } from "./SiteFormAuthFields";
import { SiteFormBasicFields } from "./SiteFormBasicFields";
type SiteFormModalProps = {
editingId: string | null;
@@ -48,441 +52,23 @@ export function SiteFormModal({
</div>
<form onSubmit={onSubmit} className="space-y-4">
<div>
<label className="block text-sm text-ink-50 mb-1.5">
站点名称 *
</label>
<input
required
className="input-base w-full"
placeholder="例如: 馒头、观众、家园"
value={form.name}
onChange={(event) =>
setForm((current) => ({ ...current, name: event.target.value }))
}
/>
</div>
<div>
<label className="block text-sm text-ink-50 mb-1.5">
站点地址 *
</label>
<input
required
className="input-base w-full"
placeholder="https://www.example.com/"
value={form.url}
onChange={(event) =>
setForm((current) => ({ ...current, url: event.target.value }))
}
/>
<p className="text-xs text-sand-500 mt-1">
格式: https://www.example.com/
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-ink-50 mb-1.5">
站点类型
</label>
<select
className="input-base w-full"
value={form.type}
onChange={(event) => onTypeChange(event.target.value)}
>
<option value="nexusphp">NexusPHP(国内主流PT)</option>
<option value="gazelle">Gazelle(HDBits等)</option>
<option value="unit3d">UNIT3D(BeyondHD等)</option>
<option value="mteam">馒头 M-Team(专用API)</option>
<option value="yemapt">YemaPT(API Auth)</option>
<option value="discuz">Discuz 论坛型</option>
<option value="custom_rss">自定义 RSS</option>
</select>
</div>
<div>
<label className="block text-sm text-ink-50 mb-1.5">状态</label>
<div className="flex items-center gap-3 h-10">
<button
type="button"
onClick={() =>
setForm((current) => ({
...current,
enabled: !current.enabled,
}))
}
className={`relative inline-flex h-5 w-9 shrink-0 rounded-full transition-colors cursor-pointer ${form.enabled ? "bg-primary-500" : "bg-gray-200"}`}
>
<span
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform mt-0.5 ${form.enabled ? "translate-x-4" : "translate-x-0.5"}`}
/>
</button>
<span
className={`text-sm ${form.enabled ? "text-white" : "text-sand-500"}`}
>
{form.enabled ? "启用" : "停用"}
</span>
</div>
</div>
</div>
{form.type === "mteam" && (
<div className="p-3 rounded-xl border border-green-500/30 bg-green-500/5">
<div className="text-sm font-medium text-green-400 mb-1">
馒头站点配置指南
</div>
<div className="text-xs text-ink-50 space-y-1">
<div>
<b>正式站地址:</b>
<code className="text-green-300">https://api.m-team.cc</code>
</div>
<div>
<b>测试站地址:</b>
<code className="text-green-300">
https://test2.m-team.cc
</code>
(开发测试环境)
</div>
<div>
<b>认证方式:</b>必须使用「API Access Token」,不要使用 Cookie
</div>
<div className="pl-3 text-sand-500">
1. 登录馒头站 → 控制台 → 实验室 → 存取令牌
<br />
2. 点击「创建令牌」,复制生成的 Token
<br />
3. 将 Token 填入下方「令牌」输入框
</div>
</div>
</div>
)}
{form.type === "yemapt" && (
<div className="p-3 rounded-xl border border-cyan-500/30 bg-cyan-500/5">
<div className="text-sm font-medium text-cyan-400 mb-1">
YemaPT 配置指南
</div>
<div className="text-xs text-ink-50 space-y-1">
<div>
<b>站点地址:</b>
<code className="text-cyan-300">https://www.yemapt.org</code>
</div>
<div>
<b>认证方式:</b>使用个人详情页创建的第三方对接专用 auth
</div>
<div className="pl-3 text-sand-500">
填入下方 API Key;后端会按 Wiki 要求通过 Authorization
请求头原样发送。
</div>
</div>
</div>
)}
<div>
<label className="block text-sm text-ink-50 mb-2">认证方式</label>
<div className="flex gap-2 mb-3">
{[
{ value: "cookie", label: "Cookie" },
{ value: "api_key", label: "API Key" },
{ value: "auth_header", label: "Auth Header" },
].map((option) => (
<button
key={option.value}
type="button"
onClick={() =>
setForm((current) => ({
...current,
auth_type: option.value,
}))
}
className={`px-3 py-1.5 rounded-xl text-xs font-medium border transition ${
form.auth_type === option.value
? "bg-primary-500 text-ink-600 border-primary-500"
: "border-gray-200 text-ink-50 hover:border-primary-500/50"
}`}
>
{option.label}
</button>
))}
</div>
{form.auth_type === "cookie" && (
<div>
<label className="block text-xs text-ink-50 mb-1">Cookie</label>
<textarea
rows={3}
className="input-base w-full resize-none text-xs font-mono"
placeholder="uid=xxx; pass=xxx; ..."
value={form.cookie}
onChange={(event) =>
setForm((current) => ({
...current,
cookie: event.target.value,
}))
}
/>
<p className="text-xs text-sand-500 mt-1">
从浏览器开发者工具的请求头中获取 Cookie 值
</p>
</div>
)}
{form.auth_type === "api_key" && (
<div>
<label className="block text-xs text-ink-50 mb-1">
令牌(API Key / Passkey)
</label>
<input
type="password"
className="input-base w-full font-mono text-sm"
placeholder={
editingId
? "留空则保留原令牌,输入新值则替换"
: "输入 API Access Token"
}
value={form.api_key}
onChange={(event) =>
setForm((current) => ({
...current,
api_key: event.target.value,
}))
}
/>
<p className="text-xs text-sand-500 mt-1">
{form.type === "mteam"
? "馒头:控制台 → 实验室 → 存取令牌;第三方工具通过 x-api-key 请求头访问"
: form.type === "yemapt"
? "YemaPT:个人详情页 → 第三方对接专用 auth;通过 Authorization 请求头原样访问"
: "站点的访问 API Key"}
</p>
</div>
)}
{form.auth_type === "auth_header" && (
<div>
<label className="block text-xs text-ink-50 mb-1">
请求头(Authorization)
</label>
<input
className="input-base w-full font-mono text-xs"
placeholder="Bearer eyJhbGciOiJIUzI1NiIs..."
value={form.auth_header}
onChange={(event) =>
setForm((current) => ({
...current,
auth_header: event.target.value,
}))
}
/>
</div>
)}
<div className="mt-4">
<label className="block text-xs text-ink-50 mb-1">RSS 地址</label>
<input
className="input-base w-full text-xs font-mono"
placeholder="https://.../torrents/rss?..."
value={form.rss_url}
onChange={(event) =>
setForm((current) => ({
...current,
rss_url: event.target.value,
}))
}
/>
<p className="text-xs text-sand-500 mt-1">
站点 RSS 订阅地址,用于获取最新资源
</p>
</div>
</div>
<div>
<button
type="button"
onClick={() => setAdvancedOpen(!advancedOpen)}
className="flex items-center gap-1 text-xs text-ink-50 hover:text-white transition"
>
{advancedOpen ? "▾" : "▸"} 高级选项
</button>
{advancedOpen && (
<div className="mt-3 pl-4 space-y-3 border-l border-gray-200">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs text-ink-50 mb-1">
User-Agent
</label>
<input
className="input-base w-full text-xs"
placeholder="自定义 UA,留空使用默认"
value={form.user_agent}
onChange={(event) =>
setForm((current) => ({
...current,
user_agent: event.target.value,
}))
}
/>
</div>
<div>
<label className="block text-xs text-ink-50 mb-1">
请求超时 (秒)
</label>
<input
type="number"
className="input-base w-full"
min={1}
max={300}
value={form.timeout}
onChange={(event) =>
setForm((current) => ({
...current,
timeout: Number(event.target.value),
}))
}
/>
</div>
<div>
<label className="block text-xs text-ink-50 mb-1">
优先级 (数字越大越优先)
</label>
<input
type="number"
className="input-base w-full"
min={1}
max={100}
value={form.priority}
onChange={(event) =>
setForm((current) => ({
...current,
priority: Number(event.target.value),
}))
}
/>
</div>
<div>
<label className="block text-xs text-ink-50 mb-1">
关联下载器
</label>
<input
className="input-base w-full text-xs"
placeholder="下载器 ID 或名称"
value={form.downloader}
onChange={(event) =>
setForm((current) => ({
...current,
downloader: event.target.value,
}))
}
/>
</div>
</div>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="h-4 w-4 accent-primary-400"
checked={form.use_proxy}
onChange={(event) =>
setForm((current) => ({
...current,
use_proxy: event.target.checked,
}))
}
/>
<span className="text-xs text-ink-100">使用代理</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="h-4 w-4 accent-primary-400"
checked={form.rate_limit}
onChange={(event) =>
setForm((current) => ({
...current,
rate_limit: event.target.checked,
}))
}
/>
<span className="text-xs text-ink-100">启用限流</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="h-4 w-4 accent-primary-400"
checked={form.browser_emulation}
onChange={(event) =>
setForm((current) => ({
...current,
browser_emulation: event.target.checked,
}))
}
/>
<span className="text-xs text-ink-100">浏览器模拟</span>
</label>
</div>
<div>
<label className="block text-xs text-ink-50 mb-1">
Extra 扩展配置 (JSON)
</label>
<textarea
rows={3}
className="input-base w-full resize-none text-xs font-mono"
placeholder='{"key":"value"}'
value={form.extra}
onChange={(event) =>
setForm((current) => ({
...current,
extra: event.target.value,
}))
}
/>
</div>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() =>
setForm((current) => ({
...current,
is_default: !current.is_default,
}))
}
className={`relative inline-flex h-5 w-9 shrink-0 rounded-full transition-colors cursor-pointer ${form.is_default ? "bg-primary-500" : "bg-gray-200"}`}
>
<span
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform mt-0.5 ${form.is_default ? "translate-x-4" : "translate-x-0.5"}`}
/>
</button>
<span className="text-sm text-ink-50">设为默认站点</span>
</div>
</div>
)}
</div>
<div className="flex justify-end gap-2 pt-2">
<button
type="button"
onClick={onClose}
className="rounded-lg border border-gray-200 px-4 py-2 text-sm text-ink-100 hover:bg-gray-50 transition"
>
取消
</button>
<button
type="submit"
disabled={saving || !form.name.trim() || !form.url.trim()}
className="neon-button text-sm disabled:opacity-50 flex items-center gap-1.5"
>
{saving ? (
<>
<RefreshCw size={14} className="animate-spin" />
保存中...
</>
) : (
"保存"
)}
</button>
</div>
<SiteFormBasicFields
form={form}
setForm={setForm}
onTypeChange={onTypeChange}
/>
<SiteFormAuthFields
editingId={editingId}
form={form}
setForm={setForm}
/>
<SiteFormAdvancedOptions
advancedOpen={advancedOpen}
setAdvancedOpen={setAdvancedOpen}
form={form}
setForm={setForm}
/>
<SiteFormActions form={form} saving={saving} onClose={onClose} />
</form>
</div>
</div>