style(copy): fix remaining punctuation violations and make check:copy strict

Auto-fix of 602 string literals across 74 files (half-width punctuation next to
Chinese, parenthesised Chinese, quotes); biome reflow; e2/e6 assertions follow
the new full-width summaries. check:copy now runs with --strict in ci.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 21:34:51 +08:00
1 parent 0fccfe257d
commit fb84a71f80
76 files changed
+582 -570

No files matched your search

@@ -60,7 +60,7 @@ export async function queryMediaServer(
let timer: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(
() => reject(new Error(`timeout: 媒体服务器 ${limits.timeoutMs / 1000}s 内未响应`)),
() => reject(new Error(`timeout:媒体服务器 ${limits.timeoutMs / 1000}s 内未响应`)),
limits.timeoutMs,
);
});
@@ -131,7 +131,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) {
method: "get",
path: "/library",
scope: "read",
summary: "媒体库列表(已登记资产或正在追踪的作品)",
summary: "媒体库列表(已登记资产或正在追踪的作品)",
query: LibraryListQuery,
response: cursorPage(LibraryItemSchema),
},
@@ -232,7 +232,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) {
method: "get",
path: "/library/{id}",
scope: "read",
summary: "作品详情:实际路径、身份映射、元数据来源、各 Slot、候选拒绝理由、下载历史与校验结果",
summary: "作品详情:实际路径、身份映射、元数据来源、各 Slot、候选拒绝理由、下载历史与校验结果",
params: IdParam,
response: LibraryDetail,
},
@@ -349,7 +349,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) {
method: "get",
path: "/inventory",
scope: "read",
summary: "库存叠加(站点工作台):按作品返回已有版本 / Slot 缺口 / 下载中",
summary: "库存叠加(站点工作台):按作品返回已有版本 / Slot 缺口 / 下载中",
query: InventoryQuery,
response: InventoryResponse,
},
@@ -469,7 +469,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) {
method: "get",
path: "/inventory/media-servers",
scope: "read",
summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)",
summary: "Emby 兼容库存查询(多实例;接口失败/截断 = unknown,不等于缺失)",
query: MediaServerInventoryQuery,
response: MediaServerInventory,
},
@@ -91,7 +91,7 @@ export async function checkSuppression(trx: Trx, fingerprint: string, source: st
.where("active", "=", true)
.executeTakeFirst();
if (!sup) return;
if (source === "agent") conflict("用户已停用或删除过同一语义的规则,Agent 不能自动加回");
if (source === "agent") conflict("用户已停用或删除过同一语义的规则,Agent 不能自动加回");
await liftSuppressions(trx, fingerprint, by);
}
@@ -114,7 +114,7 @@ async function checkIdsExist(exec: Trx | AoDeps["ctx"]["db"], ids: readonly stri
.execute();
const have = new Set(rows.map((r) => r.id));
const missing = ids.filter((x) => !have.has(x));
if (missing.length) throw new ApiException("bad_request", `规则组不存在:${missing.join(", ")}`);
if (missing.length) throw new ApiException("bad_request", `规则组不存在:${missing.join(", ")}`);
}
/** 版本目标 / 订阅引用的规则组 id 校验(供 slots 路由复用) */
@@ -253,7 +253,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) {
Api.ruleAtoms.create,
async (_c, { body }) => {
const key = String(body.key).toUpperCase();
if (BUILTIN_ATOM_MAP.has(key)) conflict(`${key} 是内置关键字,不能覆盖`);
if (BUILTIN_ATOM_MAP.has(key)) conflict(`${key} 是内置关键字,不能覆盖`);
checkAtomPatterns(body.include);
checkAtomPatterns(body.exclude);
const r = await db()
@@ -318,7 +318,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) {
(e) => atomsOf(e).has(a!.key),
),
);
if (users.length) conflict(`自定义条件 ${a!.key} 被规则组引用:${users.map((u) => u.name).join("、")}`);
if (users.length) conflict(`自定义条件 ${a!.key} 被规则组引用:${users.map((u) => u.name).join("、")}`);
await db().deleteFrom("rule_atom").where("id", "=", params.id).execute();
return { ok: true as const };
},
@@ -434,7 +434,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) {
.executeTakeFirst();
if (!cur) notFound("规则组");
if (b.expectedRevision !== undefined && b.expectedRevision !== cur!.revision)
conflict(`revision 不匹配(当前 ${cur!.revision})`);
conflict(`revision 不匹配(当前 ${cur!.revision})`);
if (b.name !== undefined && b.name !== cur!.name) {
const dupName = await trx
.selectFrom("rule_group")
@@ -458,7 +458,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) {
ladderRequired: b.ladderRequired ?? cur!.ladder_required,
};
if (b.kind !== undefined && b.kind !== cur!.kind && cur!.default_order !== null)
conflict("该规则组在默认链里,先从默认值移出再改类型");
conflict("该规则组在默认链里,先从默认值移出再改类型");
// 偏好标签迁来的组沿用旧指纹口径,规则没变就不重算
const semanticChanged =
b.kind !== undefined ||
@@ -528,7 +528,7 @@ export function registerRuleGroupRoutes(app: ModuleApp, d: AoDeps) {
.executeTakeFirst();
if (!cur) notFound("规则组");
const refs = await referenceCount(trx, cur!.id);
if (refs > 0) conflict(`规则组被 ${refs} 个版本目标或订阅引用,先解除引用`);
if (refs > 0) conflict(`规则组被 ${refs} 个版本目标或订阅引用,先解除引用`);
const row = await trx
.updateTable("rule_group")
.set({
@@ -103,7 +103,7 @@ async function assertNoDuplicate(
q = input.seasonUnitId ? q.where("season_unit_id", "=", input.seasonUnitId) : q.where("season_unit_id", "is", null);
if (input.excludeId) q = q.where("id", "!=", input.excludeId);
const hit = await q.executeTakeFirst();
if (hit) conflict(`该作品同一季已有订阅:${hit.id}`);
if (hit) conflict(`该作品同一季已有订阅:${hit.id}`);
}
/** 没有收录策略模板、表单给了质量预设时,新订阅按这个模板建一个版本目标(预设随后写入) */
@@ -497,12 +497,12 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) {
const pause = async (sub: SubscriptionRow) => {
if (sub.status === "paused") return sub;
if (!(TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status))
conflict(`订阅状态为 ${sub.status},不能暂停`);
conflict(`订阅状态为 ${sub.status},不能暂停`);
return setStatus(sub, "paused", "user_paused");
};
const resume = async (sub: SubscriptionRow) => {
if ((TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status)) return sub;
if (sub.status !== "paused") conflict(`订阅状态为 ${sub.status},已完成/已取消的订阅用"重新订阅"`);
if (sub.status !== "paused") conflict(`订阅状态为 ${sub.status},已完成/已取消的订阅用"重新订阅"`);
const ready = await tmdbReady(db(), sub.media_entity_id, sub.media_kind);
const r = await setStatus(sub, ready ? "active" : "pending", ready ? "resumed" : "metadata_pending", {
nextSearchNow: true,
@@ -511,11 +511,11 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) {
return r;
};
const search = async (sub: SubscriptionRow) => {
if (!sub.media_entity_id) throw new ApiException("bad_request", "订阅未关联作品,无法搜索");
if (!sub.media_entity_id) throw new ApiException("bad_request", "订阅未关联作品,无法搜索");
try {
return await enqueueSubscriptionSearch(d, sub.id, "manual");
} catch (err) {
throw new ApiException("upstream_unavailable", `搜索任务入队失败:${errMessage(err)}`);
throw new ApiException("upstream_unavailable", `搜索任务入队失败:${errMessage(err)}`);
}
};
@@ -623,7 +623,7 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) {
async (_c, { params, body }) => {
const sub = await load(params.id);
if (!(TRACKING_SUB_STATUSES as readonly string[]).includes(sub.status))
conflict("订阅不在追踪中(暂停/已完成/已取消),先恢复或重新订阅");
conflict("订阅不在追踪中(暂停/已完成/已取消),先恢复或重新订阅");
const slots = sub.media_entity_id
? await db()
.selectFrom("acquisition_slot")
@@ -641,7 +641,7 @@ export function registerSubscriptionRoutes(app: ModuleApp, d: AoDeps) {
{ idempotencyKey: `fill:${params.slotId}:${d.ports.now().getTime()}`, traceId: params.slotId },
);
} catch (err) {
throw new ApiException("upstream_unavailable", `补全任务入队失败:${errMessage(err)}`);
throw new ApiException("upstream_unavailable", `补全任务入队失败:${errMessage(err)}`);
}
},
{ tag: TAG, status: 202 },
@@ -801,7 +801,7 @@ async function evaluateForSlot(c: Ctx, p: SlotInput): Promise<{ planId: string |
}).then(
({ taskId }) => db.updateTable("acquisition_plan").set({ task_id: taskId }).where("id", "=", planId).execute(),
(err) =>
d.ctx.logger.warn({ err: { message: (err as Error).message }, planId }, "submitDownload 入队失败,留给巡检补投"),
d.ctx.logger.warn({ err: { message: (err as Error).message }, planId }, "submitDownload 入队失败,留给巡检补投"),
);
return { planId, deferred: false };
}
@@ -932,7 +932,7 @@ async function gates(
reasons: [b.decision === "unknown" ? "budget_unknown_size" : "budget_exceeded"],
rejected: b.violations.map((v) => ({
code: `budget.${v.kind}`,
message: `${v.kind}: 上限 ${v.limit},当前 ${v.current},请求 ${v.requested}`,
message: `${v.kind}:上限 ${v.limit},当前 ${v.current},请求 ${v.requested}`,
})),
};
}
@@ -949,7 +949,7 @@ async function gates(
rejected: [
{
code: `cache.${dv.decision}`,
message: `缓存卷 ${vol.volumeKey} 使用率 ${dv.usedPct?.toFixed(1) ?? "未知"}%,预测 ${dv.projectedPct?.toFixed(1) ?? "未知"}%`,
message: `缓存卷 ${vol.volumeKey} 使用率 ${dv.usedPct?.toFixed(1) ?? "未知"}%,预测 ${dv.projectedPct?.toFixed(1) ?? "未知"}%`,
},
],
};
@@ -151,7 +151,7 @@ export async function applySlotEvent(
return false;
}
if (!next) {
d.ctx.logger.warn({ slotId, event: event.type, from: slot.status }, "Slot 状态迁移被拒绝(已被人工处理?)");
d.ctx.logger.warn({ slotId, event: event.type, from: slot.status }, "Slot 状态迁移被拒绝(已被人工处理?)");
return false;
}
const ended = PLAN_EVENTS.has(event.type) && stageAfter(event.type) === null;
@@ -288,7 +288,7 @@ export async function submitDownload(d: AoDeps, planId: string, opts: { runId?:
return { kind: "already_exists", ...view(out.existing), tags: [...new Set([...out.existing.tags, tag])] };
}
case "accepted_unconfirmed":
if (!out.hash) throw new Error("qB 已收单但未能确认任务(无 hash),稍后按探针标签对账");
if (!out.hash) throw new Error("qB 已收单但未能确认任务(无 hash),稍后按探针标签对账");
return {
kind: "accepted_unconfirmed",
hash: out.hash,
@@ -351,7 +351,7 @@ export async function submitDownload(d: AoDeps, planId: string, opts: { runId?:
// 季包/多集包:按缺集选文件(接管的已有任务不改文件选择);失败不影响提交结果
if (result.kind === "added" && plan.unit_keys.length > 0 && plan.file_selection === null) {
await selectPackFiles(d, client, hash, plan, rel).catch((err) =>
d.ctx.logger.warn({ planId: plan.id, err: { message: errMessage(err) } }, "季包选文件失败,按整包下载"),
d.ctx.logger.warn({ planId: plan.id, err: { message: errMessage(err) } }, "季包选文件失败,按整包下载"),
);
}
if (!dtId) {
@@ -754,7 +754,7 @@ async function observeLocked(
// 无进展:只记证据 + 待处理,保留原任务,不换源(REQ §8.4.2)
await upsertDecision(d, trx, {
kind: "download_stalled",
title: `下载无进展:${t.name}`,
title: `下载无进展:${t.name}`,
summary: `已 ${Math.round((now.getTime() - (noProgressSince?.getTime() ?? now.getTime())) / 60000)} 分钟没有新增字节`,
subjectType: "download_task",
subjectId: row.id,
@@ -771,7 +771,7 @@ async function observeLocked(
if ((merged.status === "failed" || merged.status === "missing") && changed) {
await upsertDecision(d, trx, {
kind: "download_failed",
title: `下载异常:${t.name}`,
title: `下载异常:${t.name}`,
summary: `qB 状态 ${t.rawState}`,
subjectType: "download_task",
subjectId: row.id,
@@ -1133,7 +1133,7 @@ export async function cleanupDownloadCache(
kind: "space_insufficient",
severity: "critical",
title: `下载缓存卷 ${v.volumeKey} 空间不足`,
summary: `使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停`,
summary: `使用率 ${pct.toFixed(1)}%,可清理任务已用尽;新下载已暂停`,
subjectType: "cache_volume",
dedupeKey: `space_insufficient:${v.volumeKey}`,
evidence: { usedPercent: pct, locked: entry.skipped },
@@ -1146,7 +1146,7 @@ export async function cleanupDownloadCache(
/** qB 删种 + 删数据:必经 assertSafeToDeleteData(本机视图与下载器视图各查一次) */
async function removeWithData(d: AoDeps, cand: CleanupCandidate, libraryRootsLocal: string[]): Promise<boolean> {
const { row, topo } = cand;
if (!row.content_path) throw new Error("content_path 未知,不能删数据");
if (!row.content_path) throw new Error("content_path 未知,不能删数据");
const localContent = hostPathOf(topo, row.content_path);
if (!localContent) throw new Error("content_path 无法映射到本机路径");
assertSafeToDeleteData(
@@ -217,14 +217,14 @@ export function ruleFingerprint(g: {
}
const fmtErrors = (errs: readonly RuleParseError[]) =>
errs.map((e) => `${e.message}(第 ${e.layer + 1} 层,位置 ${e.position})`).join(";");
errs.map((e) => `${e.message}(第 ${e.layer + 1} 层,位置 ${e.position})`).join(";");
/** 表达式里的原子都要存在(内置且支持,或自定义) */
function checkAtoms(e: RuleExpr, custom: ReadonlySet<string>): string[] {
const bad: string[] = [];
for (const id of atomsOf(e)) {
const b = BUILTIN_ATOM_MAP.get(id);
if (b ? !b.supported : !custom.has(id)) bad.push(b ? `${id}(不支持)` : `${id}(不存在)`);
if (b ? !b.supported : !custom.has(id)) bad.push(b ? `${id}(不支持)` : `${id}(不存在)`);
}
return bad;
}
@@ -240,7 +240,7 @@ export function normalizeGroupInput(
if (body.ruleString.trim() === "") out.layers = [];
else {
const p = parseRuleString(body.ruleString, { customAtoms: custom });
if (p.errors.length) throw new ApiException("validation_failed", `规则串解析失败:${fmtErrors(p.errors)}`);
if (p.errors.length) throw new ApiException("validation_failed", `规则串解析失败:${fmtErrors(p.errors)}`);
out.layers = p.layers;
}
} else if (body.layers !== undefined) {
@@ -252,16 +252,16 @@ export function normalizeGroupInput(
if (body.hardExcludes !== undefined) {
out.hardExcludes = body.hardExcludes.map((x: string | ApiRuleExpr, i: number) => {
if (typeof x !== "string") return normalizeExpr(x as RuleExpr);
if (x.includes(">")) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条不能分层(去掉 >)`);
if (x.includes(">")) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条不能分层(去掉 >)`);
const p = parseExpr(x, { customAtoms: custom });
if (!p.expr) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条解析失败:${fmtErrors(p.errors)}`);
if (!p.expr) throw new ApiException("validation_failed", `硬排除第 ${i + 1} 条解析失败:${fmtErrors(p.errors)}`);
return p.expr;
});
}
const bad = [...(out.layers ?? []).map((l) => l.expr), ...(out.hardExcludes ?? [])].flatMap((e) =>
checkAtoms(e, custom),
);
if (bad.length) throw new ApiException("validation_failed", `条件无效:${[...new Set(bad)].join("、")}`);
if (bad.length) throw new ApiException("validation_failed", `条件无效:${[...new Set(bad)].join("、")}`);
return out;
}
@@ -83,7 +83,7 @@ async function idsByKey(
.execute();
return values.map((v) => {
const hit = rows.find((r) => r.id === v || r.k === v);
if (!hit) bad(`${label}不存在:${v}`);
if (!hit) bad(`${label}不存在:${v}`);
return hit!.id;
});
}
@@ -454,14 +454,14 @@ export async function normalizeSubscriptionParams(
const parsed = SubscriptionParams.safeParse(src);
if (!parsed.success) {
const i = parsed.error.issues[0];
bad(`params.${i?.path.join(".") ?? ""} 无效:${i?.message ?? ""}`);
bad(`params.${i?.path.join(".") ?? ""} 无效:${i?.message ?? ""}`);
}
const p = parsed.data as SubscriptionParams;
// ── 库根存在性与正则白名单 ──
if (p.libraryRootId) {
const r = await exec.selectFrom("library_root").select("id").where("id", "=", p.libraryRootId).executeTakeFirst();
if (!r) bad(`库根不存在:${p.libraryRootId}`);
if (!r) bad(`库根不存在:${p.libraryRootId}`);
}
for (const k of ["include", "exclude"] as const)
for (const pat of p[k] ?? []) {
@@ -470,7 +470,7 @@ export async function normalizeSubscriptionParams(
try {
new RegExp(pat, "i");
} catch {
bad(`${k} 正则语法错误:${pat}`);
bad(`${k} 正则语法错误:${pat}`);
}
}
if (p.stopAfter && Number.isNaN(Date.parse(p.stopAfter))) bad("stopAfter 不是有效时间");
@@ -474,7 +474,7 @@ export async function afterSlotVerified(
kind: "other",
severity: "info",
title: "疑似缺集",
summary: `已入库最高集之前缺:${desc}。总集数未知,订阅继续追踪`.slice(0, 500),
summary: `已入库最高集之前缺:${desc}。总集数未知,订阅继续追踪`.slice(0, 500),
subjectType: "acquisition_slot",
subjectId: slot.id,
dedupeKey: `suspected_missing:${slot.id}`,
@@ -549,7 +549,7 @@ export async function completeSubscription(
completed_at: d.ports.now(),
})
.execute();
d.ctx.logger.info({ subscriptionId: sub.id, reason }, "订阅完成,已写历史");
d.ctx.logger.info({ subscriptionId: sub.id, reason }, "订阅完成,已写历史");
return true;
}