mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-20 19:21:22 +08:00
280 lines
12 KiB
Swift
280 lines
12 KiB
Swift
import Foundation
|
|
|
|
public struct RssSource: Codable, Hashable, Identifiable, Sendable {
|
|
public var sourceUrl = ""
|
|
public var sourceName = ""
|
|
public var sourceIcon = ""
|
|
public var sourceGroup: String?
|
|
public var sourceComment: String?
|
|
public var enabled = true
|
|
public var variableComment: String?
|
|
public var jsLib: String?
|
|
public var enabledCookieJar: Bool? = true
|
|
public var concurrentRate: String?
|
|
public var header: String?
|
|
public var loginUrl: String?
|
|
public var loginUi: String?
|
|
public var loginCheckJs: String?
|
|
public var coverDecodeJs: String?
|
|
public var sortUrl: String?
|
|
public var singleUrl = false
|
|
public var articleStyle = 0
|
|
public var ruleArticles: String?
|
|
public var ruleNextPage: String?
|
|
public var ruleTitle: String?
|
|
public var rulePubDate: String?
|
|
public var ruleDescription: String?
|
|
public var ruleImage: String?
|
|
public var ruleLink: String?
|
|
public var ruleContent: String?
|
|
public var contentWhitelist: String?
|
|
public var contentBlacklist: String?
|
|
public var shouldOverrideUrlLoading: String?
|
|
public var style: String?
|
|
public var enableJs = true
|
|
public var loadWithBaseUrl = true
|
|
public var injectJs: String?
|
|
public var preloadJs: String?
|
|
public var startHtml: String?
|
|
public var startStyle: String?
|
|
public var startJs: String?
|
|
public var showWebLog = false
|
|
public var lastUpdateTime: Int64 = 0
|
|
public var customOrder = 0
|
|
public var type = 0
|
|
public var preload = false
|
|
public var cacheFirst = false
|
|
public var searchUrl: String?
|
|
public var id: String { sourceUrl }
|
|
|
|
public init(sourceUrl: String = "", sourceName: String = "") {
|
|
self.sourceUrl = sourceUrl
|
|
self.sourceName = sourceName
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let values = try decoder.container(keyedBy: RSSCodingKey.self)
|
|
sourceUrl = values.string("sourceUrl") ?? ""
|
|
sourceName = values.string("sourceName") ?? ""
|
|
sourceIcon = values.string("sourceIcon") ?? ""
|
|
sourceGroup = values.string("sourceGroup")
|
|
sourceComment = values.string("sourceComment")
|
|
enabled = values.bool("enabled") ?? true
|
|
variableComment = values.string("variableComment")
|
|
jsLib = values.string("jsLib")
|
|
enabledCookieJar = values.contains("enabledCookieJar") ? values.bool("enabledCookieJar") : true
|
|
concurrentRate = values.string("concurrentRate")
|
|
header = values.string("header")
|
|
loginUrl = values.string("loginUrl")
|
|
loginUi = values.jsonString("loginUi")
|
|
loginCheckJs = values.string("loginCheckJs")
|
|
coverDecodeJs = values.string("coverDecodeJs")
|
|
sortUrl = values.string("sortUrl")
|
|
singleUrl = values.bool("singleUrl") ?? false
|
|
articleStyle = values.int("articleStyle") ?? 0
|
|
ruleArticles = values.string("ruleArticles")
|
|
ruleNextPage = values.string("ruleNextPage")
|
|
ruleTitle = values.string("ruleTitle")
|
|
rulePubDate = values.string("rulePubDate")
|
|
ruleDescription = values.string("ruleDescription")
|
|
ruleImage = values.string("ruleImage")
|
|
ruleLink = values.string("ruleLink")
|
|
ruleContent = values.string("ruleContent")
|
|
contentWhitelist = values.string("contentWhitelist")
|
|
contentBlacklist = values.string("contentBlacklist")
|
|
shouldOverrideUrlLoading = values.string("shouldOverrideUrlLoading")
|
|
style = values.string("style")
|
|
enableJs = values.bool("enableJs") ?? true
|
|
loadWithBaseUrl = values.bool("loadWithBaseUrl") ?? true
|
|
injectJs = values.string("injectJs")
|
|
preloadJs = values.string("preloadJs")
|
|
startHtml = values.string("startHtml")
|
|
startStyle = values.string("startStyle")
|
|
startJs = values.string("startJs")
|
|
showWebLog = values.bool("showWebLog") ?? false
|
|
lastUpdateTime = values.int64("lastUpdateTime") ?? 0
|
|
customOrder = values.int("customOrder") ?? 0
|
|
type = values.int("type") ?? 0
|
|
preload = values.bool("preload") ?? false
|
|
cacheFirst = values.bool("cacheFirst") ?? false
|
|
searchUrl = values.string("searchUrl")
|
|
}
|
|
|
|
public var groups: [String] { BookSource.parseGroups(sourceGroup) }
|
|
public var displayNameGroup: String {
|
|
groups.isEmpty ? sourceName : "\(sourceName) (\(sourceGroup ?? ""))"
|
|
}
|
|
public static func == (lhs: Self, rhs: Self) -> Bool { lhs.sourceUrl == rhs.sourceUrl }
|
|
public func hash(into hasher: inout Hasher) { hasher.combine(sourceUrl) }
|
|
|
|
public mutating func addGroups(_ value: String) {
|
|
var merged = Set(groups)
|
|
merged.formUnion(BookSource.parseGroups(value))
|
|
sourceGroup = merged.joined(separator: ",")
|
|
}
|
|
public mutating func removeGroups(_ value: String) {
|
|
let removed = Set(BookSource.parseGroups(value))
|
|
sourceGroup = groups.filter { !removed.contains($0) }.joined(separator: ",")
|
|
}
|
|
public func displayVariableComment(_ other: String) -> String {
|
|
guard let variableComment, !variableComment.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return other }
|
|
return "\(variableComment)\n\(other)"
|
|
}
|
|
}
|
|
|
|
public struct RssArticle: Codable, Hashable, Identifiable, Sendable {
|
|
public var origin = ""
|
|
public var sort = ""
|
|
public var title = ""
|
|
public var order: Int64 = 0
|
|
public var link = ""
|
|
public var pubDate: String?
|
|
public var description: String?
|
|
public var content: String?
|
|
public var image: String?
|
|
public var group = "默认分组"
|
|
public var read = false
|
|
public var variable: String?
|
|
public var type = 0
|
|
public var durPos = 0
|
|
public var id: String { "\(origin)\u{0}\(link)\u{0}\(sort)" }
|
|
|
|
public init(origin: String = "", sort: String = "", title: String = "", link: String = "") {
|
|
self.origin = origin; self.sort = sort; self.title = title; self.link = link
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let values = try decoder.container(keyedBy: RSSCodingKey.self)
|
|
origin = values.string("origin") ?? ""; sort = values.string("sort") ?? ""
|
|
title = values.string("title") ?? ""; order = values.int64("order") ?? 0
|
|
link = values.string("link") ?? ""; pubDate = values.string("pubDate")
|
|
description = values.string("description"); content = values.string("content")
|
|
image = values.string("image"); group = values.string("group") ?? "默认分组"
|
|
read = values.bool("read") ?? false; variable = values.string("variable")
|
|
type = values.int("type") ?? 0; durPos = values.int("durPos") ?? 0
|
|
}
|
|
|
|
public func star(at time: Int64 = Int64(Date().timeIntervalSince1970 * 1_000)) -> RssStar {
|
|
.init(article: self, starTime: time)
|
|
}
|
|
public func record(at time: Int64 = Int64(Date().timeIntervalSince1970 * 1_000)) -> RssReadRecord {
|
|
.init(record: link, title: title, readTime: time, origin: origin, sort: sort, image: image, type: type, durPos: durPos, pubDate: pubDate)
|
|
}
|
|
public var variableMap: [String: String] { Self.decodeVariables(variable) }
|
|
public static func == (lhs: Self, rhs: Self) -> Bool {
|
|
lhs.origin == rhs.origin && lhs.link == rhs.link && lhs.sort == rhs.sort
|
|
}
|
|
public func hash(into hasher: inout Hasher) { hasher.combine(link) }
|
|
|
|
fileprivate static func decodeVariables(_ value: String?) -> [String: String] {
|
|
guard let value, let data = value.data(using: .utf8),
|
|
let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return [:] }
|
|
return object.reduce(into: [:]) { result, pair in
|
|
if let string = pair.value as? String { result[pair.key] = string }
|
|
else if !(pair.value is NSNull) { result[pair.key] = String(describing: pair.value) }
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct RssReadRecord: Codable, Hashable, Identifiable, Sendable {
|
|
public var record: String
|
|
public var title: String?
|
|
public var readTime: Int64?
|
|
public var read: Bool
|
|
public var origin: String
|
|
public var sort: String
|
|
public var image: String?
|
|
public var type: Int
|
|
public var durPos: Int
|
|
public var pubDate: String?
|
|
public var id: String { record }
|
|
public init(record: String, title: String? = nil, readTime: Int64? = nil, read: Bool = true, origin: String = "", sort: String = "", image: String? = nil, type: Int = 0, durPos: Int = 0, pubDate: String? = nil) {
|
|
self.record = record; self.title = title; self.readTime = readTime; self.read = read
|
|
self.origin = origin; self.sort = sort; self.image = image; self.type = type
|
|
self.durPos = durPos; self.pubDate = pubDate
|
|
}
|
|
public var article: RssArticle {
|
|
var value = RssArticle(origin: origin, sort: sort, title: title ?? "", link: record)
|
|
value.image = image; value.type = type; value.durPos = durPos; value.pubDate = pubDate
|
|
return value
|
|
}
|
|
}
|
|
|
|
public struct RssStar: Codable, Hashable, Identifiable, Sendable {
|
|
public var origin = ""
|
|
public var sort = ""
|
|
public var title = ""
|
|
public var starTime: Int64 = 0
|
|
public var link = ""
|
|
public var pubDate: String?
|
|
public var description: String?
|
|
public var content: String?
|
|
public var image: String?
|
|
public var group = "默认分组"
|
|
public var variable: String?
|
|
public var type = 0
|
|
public var durPos = 0
|
|
public var id: String { "\(origin)\u{0}\(link)" }
|
|
public init(article: RssArticle = .init(), starTime: Int64 = 0) {
|
|
origin = article.origin; sort = article.sort; title = article.title; self.starTime = starTime
|
|
link = article.link; pubDate = article.pubDate; description = article.description
|
|
content = article.content; image = article.image; group = article.group
|
|
variable = article.variable; type = article.type; durPos = article.durPos
|
|
}
|
|
public init(from decoder: Decoder) throws {
|
|
let article = try RssArticle(from: decoder)
|
|
self.init(article: article, starTime: try decoder.container(keyedBy: RSSCodingKey.self).int64("starTime") ?? 0)
|
|
}
|
|
public var article: RssArticle {
|
|
var value = RssArticle(origin: origin, sort: sort, title: title, link: link)
|
|
value.pubDate = pubDate; value.description = description; value.content = content
|
|
value.image = image; value.group = group; value.variable = variable; value.type = type; value.durPos = durPos
|
|
return value
|
|
}
|
|
public var variableMap: [String: String] { RssArticle.decodeVariables(variable) }
|
|
public func record(at time: Int64 = Int64(Date().timeIntervalSince1970 * 1_000)) -> RssReadRecord {
|
|
.init(record: link, title: title, readTime: time, origin: origin, sort: sort, image: image, type: type, durPos: durPos, pubDate: pubDate)
|
|
}
|
|
}
|
|
|
|
private struct RSSCodingKey: CodingKey {
|
|
let stringValue: String
|
|
var intValue: Int? { nil }
|
|
init?(stringValue: String) { self.stringValue = stringValue }
|
|
init?(intValue: Int) { return nil }
|
|
}
|
|
|
|
private extension KeyedDecodingContainer where Key == RSSCodingKey {
|
|
func key(_ value: String) -> Key { Key(stringValue: value)! }
|
|
func contains(_ value: String) -> Bool { contains(key(value)) }
|
|
func string(_ name: String) -> String? {
|
|
let key = key(name)
|
|
if let value = try? decode(String.self, forKey: key) { return value }
|
|
if let value = try? decode(Int64.self, forKey: key) { return String(value) }
|
|
return nil
|
|
}
|
|
func bool(_ name: String) -> Bool? {
|
|
let key = key(name)
|
|
if let value = try? decode(Bool.self, forKey: key) { return value }
|
|
if let value = try? decode(Int.self, forKey: key) { return value != 0 }
|
|
if let value = string(name)?.lowercased() { return ["true", "1", "yes"].contains(value) ? true : (["false", "0", "no"].contains(value) ? false : nil) }
|
|
return nil
|
|
}
|
|
func int(_ name: String) -> Int? {
|
|
let key = key(name)
|
|
if let value = try? decode(Int.self, forKey: key) { return value }
|
|
return string(name).flatMap(Int.init)
|
|
}
|
|
func int64(_ name: String) -> Int64? {
|
|
let key = key(name)
|
|
if let value = try? decode(Int64.self, forKey: key) { return value }
|
|
return string(name).flatMap(Int64.init)
|
|
}
|
|
func jsonString(_ name: String) -> String? {
|
|
if let value = string(name) { return value }
|
|
let key = key(name)
|
|
guard let value = try? decode(JSONValue.self, forKey: key) else { return nil }
|
|
return value.stringValue
|
|
}
|
|
}
|