Files

133 lines
4.7 KiB
Swift

import Foundation
public struct RSSFetchResult: Sendable {
public var requestURL: String
public var finalURL: String
public var statusCode: Int
public var body: String
public var report: RSSRuleParseReport
}
public struct RSSContentFetchResult: Sendable {
public var requestURL: String
public var finalURL: String
public var statusCode: Int
public var body: String
public var content: String
public var diagnostics: [RuleDiagnostic]
}
public struct RSSService: Sendable {
private let transport: any HTTPTransport
private let urlBuilder: LegadoURLBuilder
private let engine: RuleEngine
private let parser: RSSRuleParser
private let networkPolicy: SourceNetworkPolicy
private let webViewExecutor: (any SourceWebViewRequestExecuting)?
public init(
transport: any HTTPTransport = URLSessionHTTPTransport(),
urlBuilder: LegadoURLBuilder = .init(),
engine: RuleEngine = .init(),
networkPolicy: SourceNetworkPolicy = .shared,
webViewExecutor: (any SourceWebViewRequestExecuting)? = nil
) {
self.transport = transport
self.urlBuilder = urlBuilder
self.engine = engine
self.parser = RSSRuleParser(engine: engine)
self.networkPolicy = networkPolicy
self.webViewExecutor = webViewExecutor
}
public func articles(
sortName: String,
sortURL: String,
source: RssSource,
page: Int,
key: String? = nil
) async throws -> RSSRuleParseReport {
try await articlesDetailed(sortName: sortName, sortURL: sortURL, source: source, page: page, key: key).report
}
public func articlesDetailed(
sortName: String,
sortURL: String,
source: RssSource,
page: Int,
key: String? = nil
) async throws -> RSSFetchResult {
let adapter = source.bookSourceAdapter
let headers = try BookSourceHeaderResolver().resolve(adapter)
let prepared = try urlBuilder.prepare(
sortURL,
context: .init(key: key, page: page, source: adapter),
baseURL: URL(string: source.sourceUrl),
defaultHeaders: headers
)
let response = try await networkPolicy.execute(
prepared,
source: adapter,
transport: transport,
timeout: 180,
webViewExecutor: webViewExecutor,
snapshotStage: .debug
)
let report = try await parser.parse(
sortName: sortName,
sortURL: sortURL,
redirectURL: response.finalURL.absoluteString,
body: response.decodedText,
source: source,
variables: response.requestVariables
)
return .init(
requestURL: prepared.url.absoluteString,
finalURL: response.finalURL.absoluteString,
statusCode: response.statusCode,
body: response.decodedText,
report: report
)
}
public func content(article: RssArticle, rule: String, source: RssSource) async throws -> String {
try await contentDetailed(article: article, rule: rule, source: source).content
}
public func contentDetailed(article: RssArticle, rule: String, source: RssSource) async throws -> RSSContentFetchResult {
let adapter = source.bookSourceAdapter
let headers = try BookSourceHeaderResolver().resolve(adapter)
let prepared = try urlBuilder.prepare(
article.link,
context: .init(variables: article.variableMap, source: adapter),
baseURL: URL(string: article.origin),
defaultHeaders: headers
)
let response = try await networkPolicy.execute(
prepared,
source: adapter,
transport: transport,
timeout: 180,
webViewExecutor: webViewExecutor,
snapshotStage: .content
)
let result = await engine.evaluate(
rule,
inText: response.decodedText,
baseURL: URL(string: article.link, relativeTo: URL(string: article.origin))?.absoluteURL.absoluteString ?? article.link,
context: .init(variables: article.variableMap, baseURL: response.finalURL.absoluteString, source: adapter)
)
if let error = result.diagnostics.first(where: { $0.severity == .error }) {
throw RuleEngineError.malformedRule(rule: rule, detail: error.message)
}
return .init(
requestURL: prepared.url.absoluteString,
finalURL: response.finalURL.absoluteString,
statusCode: response.statusCode,
body: response.decodedText,
content: result.joinedString ?? "",
diagnostics: result.diagnostics
)
}
}