Files

88 lines
5.2 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
public enum RSSDebugStage: String, Codable, CaseIterable, Sendable {
case configuration, listRequest, listParse, contentRequest, contentParse
public var title: String {
switch self {
case .configuration: "配置"
case .listRequest: "列表请求"
case .listParse: "列表解析"
case .contentRequest: "正文请求"
case .contentParse: "正文解析"
}
}
}
public struct RSSDebugStep: Codable, Hashable, Identifiable, Sendable {
public var stage: RSSDebugStage
public var status: SourceDebugStatus
public var summary: String
public var diagnostics: [RuleDiagnostic]
public var id: RSSDebugStage { stage }
}
public struct RSSSourceDebugReport: Sendable {
public var sourceURL: String
public var input: String
public var steps: [RSSDebugStep] = []
public var listSource: String?
public var contentSource: String?
public var articles: [RssArticle] = []
public var contentPreview: String?
public var passed: Bool { !steps.contains { $0.status == .failed } && steps.contains { $0.stage == .listParse && $0.status != .skipped } }
}
public struct RSSSourceDebugger: Sendable {
private let service: RSSService
public init(service: RSSService = .init()) { self.service = service }
public func run(source: RssSource, input: String) async -> RSSSourceDebugReport {
let input = input.trimmingCharacters(in: .whitespacesAndNewlines)
var report = RSSSourceDebugReport(sourceURL: source.sourceUrl, input: input)
let listURL = resolvedListURL(source: source, input: input)
let missing = source.sourceUrl.isEmpty || listURL.isEmpty || source.sourceName.isEmpty
report.steps.append(.init(stage: .configuration, status: missing ? .failed : .passed, summary: missing ? "源名称、源URL或调试地址为空" : "使用地址:\(listURL)", diagnostics: []))
guard !missing else { appendSkipped(after: .configuration, to: &report, reason: "配置不完整"); return report }
do {
let fetched = try await service.articlesDetailed(sortName: input, sortURL: listURL, source: source, page: 1, key: input.isEmpty ? nil : input)
report.listSource = fetched.body
report.steps.append(.init(stage: .listRequest, status: (200..<400).contains(fetched.statusCode) ? .passed : .warning, summary: "HTTP \(fetched.statusCode);\(fetched.finalURL)", diagnostics: []))
report.articles = fetched.report.result.articles
report.steps.append(.init(stage: .listParse, status: report.articles.isEmpty ? .failed : (fetched.report.diagnostics.isEmpty ? .passed : .warning), summary: report.articles.isEmpty ? "没有解析到文章" : "解析到 \(report.articles.count) 篇文章", diagnostics: fetched.report.diagnostics))
guard let article = report.articles.first, let rule = source.ruleContent, !rule.isEmpty else {
report.steps.append(.init(stage: .contentRequest, status: .skipped, summary: "没有样本文章或未配置正文规则", diagnostics: []))
report.steps.append(.init(stage: .contentParse, status: .skipped, summary: "正文将直接使用描述或网页", diagnostics: []))
return report
}
let content = try await service.contentDetailed(article: article, rule: rule, source: source)
report.contentSource = content.body
report.steps.append(.init(stage: .contentRequest, status: (200..<400).contains(content.statusCode) ? .passed : .warning, summary: "HTTP \(content.statusCode);\(content.finalURL)", diagnostics: []))
let cleaned = content.content.trimmingCharacters(in: .whitespacesAndNewlines)
report.contentPreview = cleaned.isEmpty ? nil : String(cleaned.prefix(400))
report.steps.append(.init(stage: .contentParse, status: cleaned.isEmpty ? .failed : (content.diagnostics.isEmpty ? .passed : .warning), summary: cleaned.isEmpty ? "正文结果为空" : "正文结果 \(cleaned.count) 字", diagnostics: content.diagnostics))
} catch {
let reachedList = report.steps.contains { $0.stage == .listRequest }
let stage: RSSDebugStage = reachedList ? .contentRequest : .listRequest
report.steps.append(.init(stage: stage, status: .failed, summary: error.localizedDescription, diagnostics: []))
appendSkipped(after: stage, to: &report, reason: "前一阶段失败")
}
return report
}
private func resolvedListURL(source: RssSource, input: String) -> String {
if let separator = input.range(of: "::") { return String(input[separator.upperBound...]) }
if !input.isEmpty, let search = source.searchUrl, !search.isEmpty { return search }
return source.sourceUrl
}
private func appendSkipped(after stage: RSSDebugStage, to report: inout RSSSourceDebugReport, reason: String) {
guard let index = RSSDebugStage.allCases.firstIndex(of: stage) else { return }
let existing = Set(report.steps.map(\.stage))
for value in RSSDebugStage.allCases.dropFirst(index + 1) where !existing.contains(value) {
report.steps.append(.init(stage: value, status: .skipped, summary: reason, diagnostics: []))
}
}
}