mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-23 20:31:22 +08:00
feat: open-source 小浣熊阅读 for iOS
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import Foundation
|
||||
|
||||
public enum AddBookURLError: LocalizedError, Equatable {
|
||||
case invalidURL
|
||||
case sourceNotFound
|
||||
case detailFailed
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL: "书籍地址格式不对"
|
||||
case .sourceNotFound: "未找到匹配书源"
|
||||
case .detailFailed: "匹配书源未能解析书籍详情"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct AddBookURLResult: Sendable {
|
||||
public var source: BookSource
|
||||
public var book: Book
|
||||
public var diagnostics: [RuleDiagnostic]
|
||||
}
|
||||
|
||||
public struct AddBookURLService: Sendable {
|
||||
private let detailService: NetworkBookService
|
||||
public init(detailService: NetworkBookService = .init()) { self.detailService = detailService }
|
||||
|
||||
public func resolve(bookURL: String, sources: [BookSource]) async throws -> AddBookURLResult {
|
||||
let parsed = Self.parse(bookURL)
|
||||
guard let url = URL(string: parsed.url), let scheme = url.scheme?.lowercased(), ["http", "https"].contains(scheme), let host = url.host else {
|
||||
throw AddBookURLError.invalidURL
|
||||
}
|
||||
let baseURL = "\(scheme)://\(host)" + (url.port.map { ":\($0)" } ?? "")
|
||||
let enabled = sources.filter(\.enabled).sorted { $0.customOrder < $1.customOrder }
|
||||
var candidates: [BookSource] = []
|
||||
if let origin = parsed.origin, let source = sources.first(where: { $0.bookSourceUrl == origin }) { candidates.append(source) }
|
||||
if let source = enabled.first(where: { $0.bookSourceUrl == baseURL }) { candidates.append(source) }
|
||||
for source in enabled where source.bookUrlPattern?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false && source.bookUrlPattern != "NONE" {
|
||||
guard let rule = source.bookUrlPattern, let expression = try? NSRegularExpression(pattern: rule) else { continue }
|
||||
let range = NSRange(parsed.url.startIndex..., in: parsed.url)
|
||||
if let match = expression.firstMatch(in: parsed.url, range: range), match.range == range { candidates.append(source) }
|
||||
}
|
||||
var seen = Set<String>()
|
||||
for source in candidates where seen.insert(source.bookSourceUrl).inserted {
|
||||
let searchBook = SearchBook(bookURL: bookURL, origin: source.bookSourceUrl, originName: source.bookSourceName, type: source.bookSourceType, name: "")
|
||||
let detail = await detailService.loadDetail(searchBook: searchBook, source: source)
|
||||
if !detail.book.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return .init(source: source, book: detail.book, diagnostics: detail.diagnostics)
|
||||
}
|
||||
}
|
||||
if candidates.isEmpty { throw AddBookURLError.sourceNotFound }
|
||||
throw AddBookURLError.detailFailed
|
||||
}
|
||||
|
||||
public static func matchingSource(bookURL: String, sources: [BookSource]) -> BookSource? {
|
||||
let parsed = parse(bookURL)
|
||||
if let origin = parsed.origin, let source = sources.first(where: { $0.bookSourceUrl == origin }) { return source }
|
||||
guard let url = URL(string: parsed.url), let scheme = url.scheme, let host = url.host else { return nil }
|
||||
let baseURL = "\(scheme)://\(host)" + (url.port.map { ":\($0)" } ?? "")
|
||||
let enabled = sources.filter(\.enabled).sorted { $0.customOrder < $1.customOrder }
|
||||
if let source = enabled.first(where: { $0.bookSourceUrl == baseURL }) { return source }
|
||||
return enabled.first { source in
|
||||
guard let rule = source.bookUrlPattern?.trimmingCharacters(in: .whitespacesAndNewlines), !rule.isEmpty, rule != "NONE",
|
||||
let expression = try? NSRegularExpression(pattern: rule) else { return false }
|
||||
let range = NSRange(parsed.url.startIndex..., in: parsed.url)
|
||||
return expression.firstMatch(in: parsed.url, range: range)?.range == range
|
||||
}
|
||||
}
|
||||
|
||||
private static func parse(_ value: String) -> (url: String, origin: String?) {
|
||||
guard let expression = try? NSRegularExpression(pattern: #"\s*,\s*(?=\{)"#),
|
||||
let match = expression.firstMatch(in: value, range: NSRange(value.startIndex..., in: value)),
|
||||
let range = Range(match.range, in: value) else { return (value, nil) }
|
||||
let url = String(value[..<range.lowerBound])
|
||||
let option = String(value[range.upperBound...])
|
||||
guard let data = option.data(using: .utf8), let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return (url, nil) }
|
||||
return (url, object["origin"] as? String ?? object["bookSourceUrl"] as? String)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user