文案(站点 / 字幕):重写站点卡、凭证、RSS、Cloudflare 与补字幕文案
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
0bf26b78c6
commit
03c5f668ac
9 files changed
+30
-30
No files matched your search
@@ -118,7 +118,7 @@ function AccountBlock({
|
||||
onConfirm={async () => {
|
||||
try {
|
||||
await del.mutateAsync(a.id);
|
||||
toast.success(`已删除账号 ${a.label}`);
|
||||
toast.success(`已删除账号「${a.label}」`);
|
||||
} catch (err) {
|
||||
toast.error(errorMessage(err, "删除失败"));
|
||||
throw err;
|
||||
@@ -260,7 +260,7 @@ function PasskeyForm({ site, label, has }: { site: SiteListItem; label: string;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<h2 className="text-lead font-semibold text-foreground">站点 Passkey(RSS 与下载链接用)</h2>
|
||||
<h2 className="text-lead font-semibold text-foreground">站点 Passkey</h2>
|
||||
<p className="mb-3 text-caption text-muted-foreground">
|
||||
用于 RSS 和下载链接,不参与登录。{has ? "填写新值保存会替换。" : "还没有保存。"}
|
||||
</p>
|
||||
|
||||
@@ -375,7 +375,7 @@ export function SiteAddDialog({ onClose, initialKind }: { onClose: () => void; i
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-3 text-meta text-muted-foreground">
|
||||
"未实测"的定义没有在真实站点上验证过,列表和登录可能失败。
|
||||
标「未实测」的站点定义没有在真实站点上验证过,列表和登录可能失败。
|
||||
</p>
|
||||
</div>
|
||||
) : step === 2 ? (
|
||||
|
||||
@@ -183,7 +183,7 @@ function CheckButtons({ site, checks, compact }: Omit<Props, "onDialog"> & { com
|
||||
size={compact ? "icon-sm" : "sm"}
|
||||
disabled={h || !site.enabled}
|
||||
aria-label="检测健康"
|
||||
title={site.enabled ? "检测健康:首页连通 + 登录态,走站点限流" : "站点已停用"}
|
||||
title={site.enabled ? "检测健康:首页能否打开、登录态是否有效,计入站点限流" : "站点已停用"}
|
||||
onClick={() => void checks.checkHealth(site)}
|
||||
>
|
||||
{h ? <LoaderCircle className="animate-spin" /> : <Activity />}
|
||||
@@ -194,7 +194,7 @@ function CheckButtons({ site, checks, compact }: Omit<Props, "onDialog"> & { com
|
||||
size={compact ? "icon-sm" : "sm"}
|
||||
disabled={l || !hasAccount}
|
||||
aria-label="刷新登录"
|
||||
title={hasAccount ? "刷新登录:用凭证刷新登录状态并拉取用户数据" : "先填写凭证"}
|
||||
title={hasAccount ? "刷新登录:用凭证重新登录并拉取用户数据" : "先填写凭证"}
|
||||
onClick={() => void checks.refreshLogin(site)}
|
||||
>
|
||||
{l ? <LoaderCircle className="animate-spin" /> : <RefreshCw />}
|
||||
@@ -289,7 +289,7 @@ function FeedSiteCard({ site, onDialog }: Omit<Props, "checks">) {
|
||||
<div aria-hidden className="min-h-4 flex-1" />
|
||||
<footer className="mt-auto flex flex-wrap items-center justify-end gap-2 border-t border-border/60 pt-3">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to={`${consolePath("sites", site.id)}?tab=rss`}>{f.loaded && !f.count ? "添加 RSS" : "管理 RSS"}</Link>
|
||||
<Link to={`${consolePath("sites", site.id)}?tab=rss`}>{f.loaded && !f.count ? "新建 RSS" : "管理 RSS"}</Link>
|
||||
</Button>
|
||||
<SiteMoreMenu site={site} onDialog={onDialog} />
|
||||
</footer>
|
||||
|
||||
@@ -507,7 +507,7 @@ export function SiteEditSheet({ site, onClose }: { site: SiteListItem; onClose:
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-meta text-muted-foreground">User-Agent 随凭证保存,在"更新凭证"里填写。</p>
|
||||
<p className="text-meta text-muted-foreground">User-Agent 随凭证保存,在「更新凭证」里填写。</p>
|
||||
</div>
|
||||
</DetailSheet>
|
||||
);
|
||||
@@ -544,9 +544,9 @@ export function DeleteSiteDialog({
|
||||
}}
|
||||
>
|
||||
<ul className="list-disc space-y-1 pl-5 text-caption text-muted-foreground">
|
||||
<li>站点、{site.accounts?.length ?? 0} 个账号和全部凭证删除</li>
|
||||
<li>已抓到的资源保留,标为"站点已关闭",不再产生新候选</li>
|
||||
<li>该站 RSS 停用;已有下载任务按原策略继续</li>
|
||||
<li>站点、{site.accounts?.length ?? 0} 个账号和全部凭证都会删除</li>
|
||||
<li>已抓到的资源保留,标为「站点已关闭」,不再进入收录评估</li>
|
||||
<li>这个站点的 RSS 停用;已有下载任务继续</li>
|
||||
<li>同一站点以后可以重新添加</li>
|
||||
</ul>
|
||||
</ConfirmDialog>
|
||||
|
||||
@@ -70,7 +70,7 @@ export function SiteFailureSamples({ siteId, items }: { siteId: string; items: S
|
||||
if (!items.length) return null;
|
||||
return (
|
||||
<section className="grid gap-2">
|
||||
<h3 className="text-meta text-muted-foreground">最近的失败页面(已脱敏,留最近 5 份)</h3>
|
||||
<h3 className="text-meta text-muted-foreground">最近的失败页面(已脱敏,最多保留 5 份)</h3>
|
||||
<ul className="grid gap-2">
|
||||
{items.map((s) => (
|
||||
<FailureSampleRow key={s.id} siteId={siteId} sample={s} />
|
||||
|
||||
@@ -23,7 +23,7 @@ import { cn } from "~/lib/utils.ts";
|
||||
const PURPOSES: { value: FeedPurpose; label: string; hint: string }[] = [
|
||||
{ value: "generic", label: "发现", hint: "全站新种,进入识别和收录评估" },
|
||||
{ value: "search", label: "追踪", hint: "按关键词、分类或收藏筛过的 RSS" },
|
||||
{ value: "promotion", label: "促销", hint: "只含免费 / 促销种子的 RSS" },
|
||||
{ value: "promotion", label: "促销", hint: "只含免费 / 促销资源的 RSS" },
|
||||
];
|
||||
|
||||
/** 首抓最多等 90 秒(站点限流排队);超时后提示去列表看 */
|
||||
@@ -106,7 +106,7 @@ export function SiteFeedDialog({
|
||||
<Dialog open onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加 {site.name} RSS</DialogTitle>
|
||||
<DialogTitle>新建 RSS · {site.name}</DialogTitle>
|
||||
<DialogDescription>地址加密保存,页面上只显示去掉 passkey 的地址。</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -219,7 +219,7 @@ export function SiteFeedDialog({
|
||||
</FormField>
|
||||
|
||||
<p className="text-caption text-muted-foreground">
|
||||
{account ? `账号 ${account.label}` : "该站没有账号,按地址里的 passkey 抓取"} · 解析器按站点定义 · 每 30
|
||||
{account ? `账号 ${account.label}` : "这个站点没有账号,按地址里的 passkey 抓取"} · 解析器按站点定义 · 每 30
|
||||
分钟抓一次,可在 RSS 详情里改
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("cloudflareLine / cloudflareTechItems(CF-2)", () => {
|
||||
const until = new Date("2026-09-30T17:30:00+08:00");
|
||||
const line = cloudflareLine(cfg({ lastChallengeAt: at, browserUntil: until.toISOString() }), now);
|
||||
const hm = `${String(until.getHours()).padStart(2, "0")}:${String(until.getMinutes()).padStart(2, "0")}`;
|
||||
expect(line?.text).toBe(`Cloudflare:浏览器代理中,至 ${hm}`);
|
||||
expect(line?.text).toBe(`Cloudflare:浏览器代抓中,到 ${hm}`);
|
||||
expect(cloudflareLine(cfg({ lastChallengeAt: at, lastSolvedBy: "vision" }), now)?.text).toBe(
|
||||
"Cloudflare:自动通过",
|
||||
);
|
||||
@@ -63,8 +63,8 @@ describe("cloudflareLine / cloudflareTechItems(CF-2)", () => {
|
||||
}),
|
||||
);
|
||||
expect(items.map((i) => i.label)).toEqual([
|
||||
"最近遇到挑战",
|
||||
"挑战规则",
|
||||
"最近遇到验证",
|
||||
"验证规则",
|
||||
"处理结果",
|
||||
"最近通过方式",
|
||||
"cf_clearance 复用",
|
||||
|
||||
@@ -158,7 +158,7 @@ export function healthMessage(h: SiteLastHealth | null): string | null {
|
||||
|
||||
const HEALTH_REASON_TEXT: Record<string, string> = {
|
||||
logged_in: "首页有登录标志",
|
||||
user_data: "首页没找到登录标志,按用户数据解析成功判为已登录",
|
||||
user_data: "首页没有登录标志,但用户数据解析成功,按已登录处理",
|
||||
login_page: "返回登录页",
|
||||
permission_page: "返回权限提示页",
|
||||
challenge: "疑似人机验证",
|
||||
@@ -198,7 +198,7 @@ export function cloudflareLine(
|
||||
if (st.browserUntil && Date.parse(st.browserUntil) > now.getTime()) {
|
||||
const t = new Date(st.browserUntil);
|
||||
const hm = `${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}`;
|
||||
return { text: `Cloudflare:浏览器代理中,至 ${hm}`, tone: "neutral", takeover: false };
|
||||
return { text: `Cloudflare:浏览器代抓中,到 ${hm}`, tone: "neutral", takeover: false };
|
||||
}
|
||||
if (st.lastSolvedBy) return { text: "Cloudflare:自动通过", tone: "success", takeover: false };
|
||||
if (st.lastError) return { text: "Cloudflare:没能通过", tone: "warning", takeover: false };
|
||||
@@ -206,8 +206,8 @@ export function cloudflareLine(
|
||||
}
|
||||
|
||||
const CF_SOLVED_BY: Record<string, string> = {
|
||||
auto: "等它自动放行",
|
||||
vision: "视觉子流程点了复选框",
|
||||
auto: "自动放行",
|
||||
vision: "自动点了验证复选框",
|
||||
human: "你在原站视图里通过",
|
||||
};
|
||||
const CF_RESULT: Record<string, string> = {
|
||||
@@ -227,8 +227,8 @@ export function cloudflareTechItems(config: unknown): { label: string; value: st
|
||||
if (!st?.lastChallengeAt) return [];
|
||||
const t = (v: string | null) => (v ? new Date(v).toLocaleString("zh-CN", { hour12: false }) : "");
|
||||
return [
|
||||
{ label: "最近遇到挑战", value: t(st.lastChallengeAt) },
|
||||
{ label: "挑战规则", value: st.lastRule ?? "", mono: true },
|
||||
{ label: "最近遇到验证", value: t(st.lastChallengeAt) },
|
||||
{ label: "验证规则", value: st.lastRule ?? "", mono: true },
|
||||
{ label: "处理结果", value: st.lastResult ? `${CF_RESULT[st.lastResult] ?? st.lastResult}(${st.lastResult})` : "" },
|
||||
{ label: "最近通过方式", value: st.lastSolvedBy ? (CF_SOLVED_BY[st.lastSolvedBy] ?? st.lastSolvedBy) : "" },
|
||||
{ label: "最近通过时间", value: t(st.lastSolvedAt) },
|
||||
@@ -236,7 +236,7 @@ export function cloudflareTechItems(config: unknown): { label: string; value: st
|
||||
label: "cf_clearance 复用",
|
||||
value: st.cfReuse === "ok" ? "有效(ok)" : st.cfReuse === "fails" ? "无效(fails)" : "",
|
||||
},
|
||||
{ label: "浏览器代理至", value: t(st.browserUntil) },
|
||||
{ label: "浏览器代抓到", value: t(st.browserUntil) },
|
||||
{ label: "最近尝试", value: t(st.lastAttemptAt) },
|
||||
{ label: "最近错误", value: st.lastError ?? "" },
|
||||
].filter((it) => it.value);
|
||||
@@ -317,7 +317,7 @@ export function loginFailureText(
|
||||
return { text: "登录失败:站点要求二次验证。填写 TOTP 密钥后重试,或改用 Cookie", suggestCookie: true };
|
||||
case "challenge":
|
||||
return {
|
||||
text: "站点要求人机验证(Cloudflare)。会用浏览器自动过,过不去时在铃铛里提醒你",
|
||||
text: "站点要求人机验证(Cloudflare)。会先用浏览器自动通过,失败时在铃铛里提醒",
|
||||
suggestCookie: false,
|
||||
};
|
||||
case "bad_credentials":
|
||||
@@ -333,11 +333,11 @@ export function loginFailureText(
|
||||
suggestCookie: false,
|
||||
};
|
||||
case "network":
|
||||
return { text: `连不上站点${message ? `:${message}` : ""}`, suggestCookie: false };
|
||||
return { text: `连不上站点${message ? `:${message}` : ""}`, suggestCookie: false };
|
||||
case "unsupported":
|
||||
return { text: message ?? "这个站点不支持该凭证类型的自动登录", suggestCookie: false };
|
||||
return { text: message ?? "这个站点不支持用这种凭证自动登录", suggestCookie: false };
|
||||
default:
|
||||
return { text: message ?? "登录失败,原因未知", suggestCookie: false };
|
||||
return { text: message ?? "登录失败", suggestCookie: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ export const REASON_SHORT: Record<SiteLoginFailureReason, string> = {
|
||||
two_factor: "需二次验证",
|
||||
bad_credentials: "密码错误",
|
||||
account_blocked: "账号封禁",
|
||||
challenge: "Cloudflare 挑战",
|
||||
challenge: "Cloudflare 验证",
|
||||
auth_expired: "凭证失效",
|
||||
network: "网络失败",
|
||||
unsupported: "不支持",
|
||||
|
||||
@@ -470,7 +470,7 @@ function OnlinePanel({
|
||||
) : null}
|
||||
{data && !data.noSites && !data.results.length ? (
|
||||
<p className="text-caption text-muted-foreground">
|
||||
没搜到(用了:{data.keywords.join("、") || "无"})。可以换个关键词,或去掉季集限制再试。
|
||||
没有找到字幕(搜索词:{data.keywords.join("、") || "无"})。换个关键词,或去掉季集限制再试。
|
||||
</p>
|
||||
) : null}
|
||||
{data?.results.length ? (
|
||||
|
||||
Reference in new issue
Block a user