export interface RecipientConfig { qq: string; anonQq: string; } export type DeliveryTargetStatus = "reachable" | "not_added" | "unreachable" | "unknown"; export interface DeliveryTargetHealth { platform?: string; selfId?: string; channelId?: string; channelType?: string; recipientId?: string; status: DeliveryTargetStatus; source?: string; detail?: string; } export type RecipientReadinessCode = | "ready" | "missing_target" | "invalid_target" | "target_mismatch" | "target_not_added" | "target_unreachable" | "health_unavailable"; export interface RecipientReadiness { ok: boolean; code: RecipientReadinessCode; healthStatus: DeliveryTargetStatus; message: string; } const QQ_ID_PATTERN = /^\d{5,12}$/; function isObject(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function configuredQq(value: unknown, path: string, fallback: string): string { if (value === undefined) return fallback; if (typeof value !== "string") { throw new Error(`配置项 ${path} 必须是 QQ 号字符串`); } // A fresh public installation has no personal target. It can load normally, // but evaluateRecipient() refuses activation until both QQ ids are valid. return value.trim(); } export function normalizeRecipientConfig(value: unknown, defaults: RecipientConfig): RecipientConfig { if (value === undefined) return { ...defaults }; if (!isObject(value)) throw new Error("配置项 recipient 必须是 JSON 对象"); const unknown = Object.keys(value).filter((key) => !["qq", "anonQq"].includes(key)); if (unknown.length) throw new Error(`配置项 recipient 包含未知字段:${unknown.join(", ")}`); return { qq: configuredQq(value.qq, "recipient.qq", defaults.qq), anonQq: configuredQq(value.anonQq, "recipient.anonQq", defaults.anonQq), }; } export function isValidQq(value: string): boolean { return QQ_ID_PATTERN.test(value); } export function deliveryTargetStatus(value: unknown): DeliveryTargetStatus { return value === "reachable" || value === "not_added" || value === "unreachable" || value === "unknown" ? value : "unknown"; } export function formatTargetStatus(status: DeliveryTargetStatus): string { switch (status) { case "reachable": return "reachable"; case "not_added": return "not_added (目标可能尚未添加 Anon)"; case "unreachable": return "unreachable (服务端报告目标不可达)"; case "unknown": return "unknown (服务端未提供好友状态检查)"; } } export function recipientConfigSummary(config: RecipientConfig): string { const qq = config.qq || "missing"; const anonQq = config.anonQq || "missing"; return `Anon QQ=${anonQq}, target QQ=${qq}`; } export function evaluateRecipient( config: RecipientConfig, health?: DeliveryTargetHealth, healthError?: string, ): RecipientReadiness { const healthStatus = health?.status ?? "unknown"; if (!config.qq || !config.anonQq) { return { ok: false, code: "missing_target", healthStatus, message: `Anon 通知尚未启用:目标 QQ 配置不完整(${recipientConfigSummary(config)})。请先配置目标 QQ 和 Anon QQ,再重试裸命令 /anon-notify。`, }; } if (!isValidQq(config.qq) || !isValidQq(config.anonQq)) { return { ok: false, code: "invalid_target", healthStatus, message: `Anon 通知尚未启用:QQ 配置无效(${recipientConfigSummary(config)})。QQ 号应为 5 到 12 位数字,请修正配置后重试。`, }; } if (health?.recipientId && health.recipientId !== config.qq) { return { ok: false, code: "target_mismatch", healthStatus, message: `Anon 通知尚未启用:本地目标 QQ ${config.qq} 与服务端配置的目标 QQ ${health.recipientId} 不一致。请先统一配置后重试。${health.detail ? ` 服务端信息:${health.detail}` : ""}`, }; } if (healthStatus === "not_added") { return { ok: false, code: "target_not_added", healthStatus, message: `Anon 通知尚未启用:服务端报告目标 QQ ${config.qq} 可能还没有添加 Anon(Anon QQ:${config.anonQq})。请先在 QQ 中添加 Anon,或先向 Anon 发起一次私聊,然后重新执行裸命令 /anon-notify。${health?.detail ? ` 服务端信息:${health.detail}` : ""}`, }; } if (healthStatus === "unreachable") { return { ok: false, code: "target_unreachable", healthStatus, message: `Anon 通知尚未启用:服务端报告目标 QQ ${config.qq} 当前不可达(Anon QQ:${config.anonQq})。请先确认目标已添加 Anon 并可以接收私聊,之后重新执行裸命令 /anon-notify。${health?.detail ? ` 服务端信息:${health.detail}` : ""}`, }; } if (healthError) { return { ok: true, code: "health_unavailable", healthStatus: "unknown", message: `目标 QQ 配置有效(${recipientConfigSummary(config)}),但暂时无法核验服务端状态:${healthError}。这不表示目标未添加 Anon;如发送失败,请先确认目标 QQ 已添加 Anon 后再重试。`, }; } return { ok: true, code: "ready", healthStatus, message: healthStatus === "unknown" ? `目标 QQ 配置有效(${recipientConfigSummary(config)}),服务端未提供可靠的好友状态检查;不会据此判断目标是否已添加 Anon。` : `目标 QQ 配置和服务端状态可用(${recipientConfigSummary(config)},status=${formatTargetStatus(healthStatus)})。`, }; }