217 lines
6.4 KiB
Swift
217 lines
6.4 KiB
Swift
import Foundation
|
|
|
|
// MARK: - 灵活类型解码器
|
|
|
|
/// 兼容 JSON 中数值字段可能是字符串 "0" 或整数 0 的情况
|
|
/// Android Gson 自动处理此差异,Swift 需要手动兼容
|
|
struct FlexibleInt: Codable, Hashable {
|
|
let value: Int
|
|
|
|
init(_ value: Int) {
|
|
self.value = value
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if let intVal = try? container.decode(Int.self) {
|
|
self.value = intVal
|
|
} else if let stringVal = try? container.decode(String.self),
|
|
let intVal = Int(stringVal) {
|
|
self.value = intVal
|
|
} else {
|
|
self.value = 0
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
try container.encode(value)
|
|
}
|
|
}
|
|
|
|
/// 兼容 ext 字段可能是字符串或对象
|
|
struct FlexibleExt: Codable, Hashable {
|
|
let stringValue: String?
|
|
let dictValue: [String: AnyCodableValue]?
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if let str = try? container.decode(String.self) {
|
|
self.stringValue = str
|
|
self.dictValue = nil
|
|
} else if let dict = try? container.decode([String: AnyCodableValue].self) {
|
|
self.stringValue = nil
|
|
self.dictValue = dict
|
|
} else {
|
|
self.stringValue = nil
|
|
self.dictValue = nil
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
if let str = stringValue {
|
|
try container.encode(str)
|
|
} else if let dict = dictValue {
|
|
try container.encode(dict)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 通用的 JSON 值类型,用于处理混合类型的字典
|
|
enum AnyCodableValue: Codable, Hashable {
|
|
case string(String)
|
|
case int(Int)
|
|
case double(Double)
|
|
case bool(Bool)
|
|
case dict([String: AnyCodableValue])
|
|
case array([AnyCodableValue])
|
|
case null
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if let str = try? container.decode(String.self) {
|
|
self = .string(str)
|
|
} else if let int = try? container.decode(Int.self) {
|
|
self = .int(int)
|
|
} else if let double = try? container.decode(Double.self) {
|
|
self = .double(double)
|
|
} else if let bool = try? container.decode(Bool.self) {
|
|
self = .bool(bool)
|
|
} else if let dict = try? container.decode([String: AnyCodableValue].self) {
|
|
self = .dict(dict)
|
|
} else if let array = try? container.decode([AnyCodableValue].self) {
|
|
self = .array(array)
|
|
} else {
|
|
self = .null
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.singleValueContainer()
|
|
switch self {
|
|
case .string(let s): try container.encode(s)
|
|
case .int(let i): try container.encode(i)
|
|
case .double(let d): try container.encode(d)
|
|
case .bool(let b): try container.encode(b)
|
|
case .dict(let d): try container.encode(d)
|
|
case .array(let a): try container.encode(a)
|
|
case .null: try container.encodeNil()
|
|
}
|
|
}
|
|
|
|
/// 将可能的任意类型转换为字符串(如果原本是字典或数组,则转为 JSON 字符串)
|
|
var stringValue: String? {
|
|
switch self {
|
|
case .string(let s): return s
|
|
case .int(let i): return String(i)
|
|
case .double(let d): return String(d)
|
|
case .bool(let b): return b ? "true" : "false"
|
|
case .null: return nil
|
|
case .dict, .array:
|
|
if let data = try? JSONEncoder().encode(self),
|
|
let str = String(data: data, encoding: .utf8) {
|
|
return str
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 解析接口配置
|
|
|
|
/// 解析接口配置 - 对应 Android 版 ParseBean.java
|
|
struct ParseBean: Codable, Identifiable, Hashable {
|
|
var id: String { name }
|
|
var name: String = ""
|
|
var url: String = ""
|
|
var type: Int = 0 // 0:嗅探 1:解析
|
|
var ext: [String: String]? = nil
|
|
|
|
init(name: String = "", url: String = "", type: Int = 0) {
|
|
self.name = name
|
|
self.url = url
|
|
self.type = type
|
|
}
|
|
}
|
|
|
|
// MARK: - API 配置顶层结构
|
|
|
|
/// API 配置顶层结构 - 对应 JSON 配置文件格式
|
|
struct AppConfigData: Codable {
|
|
var spider: String?
|
|
var wallpaper: String?
|
|
var sites: [SiteConfig]?
|
|
var parses: [ParseConfig]?
|
|
var lives: [LiveConfig]?
|
|
var doh: [DoHConfig]?
|
|
var rules: [RuleConfig]?
|
|
var hosts: [String]?
|
|
var flags: [String]?
|
|
var ads: [String]?
|
|
|
|
struct SiteConfig: Codable {
|
|
var key: String?
|
|
var name: String?
|
|
var api: String?
|
|
var searchable: FlexibleInt?
|
|
var filterable: FlexibleInt?
|
|
var quickSearch: FlexibleInt?
|
|
var playerType: FlexibleInt?
|
|
var type: FlexibleInt?
|
|
var ext: AnyCodableValue?
|
|
var jar: String?
|
|
var style: AnyCodableValue?
|
|
var playUrl: String?
|
|
var categories: [String]?
|
|
var click: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case key, name, api, searchable, filterable, quickSearch
|
|
case playerType, type, ext, jar, style, playUrl, categories, click
|
|
// type_flag 也可能出现
|
|
}
|
|
}
|
|
|
|
struct ParseConfig: Codable {
|
|
var name: String?
|
|
var url: String?
|
|
var type: FlexibleInt?
|
|
var ext: FlexibleExt?
|
|
}
|
|
|
|
struct LiveConfig: Codable {
|
|
var name: String?
|
|
var url: String?
|
|
var type: FlexibleInt?
|
|
var ua: String?
|
|
var epg: String?
|
|
var logo: String?
|
|
var pass: AnyCodableValue? // Android 配置中存在
|
|
var playerType: FlexibleInt?
|
|
var channels: [LiveChannelConfig]?
|
|
|
|
struct LiveChannelConfig: Codable {
|
|
var name: String?
|
|
var urls: [String]?
|
|
var group: String?
|
|
var logo: String?
|
|
}
|
|
}
|
|
|
|
struct DoHConfig: Codable {
|
|
var name: String?
|
|
var url: String?
|
|
}
|
|
|
|
struct RuleConfig: Codable {
|
|
var name: String?
|
|
var host: String?
|
|
var hosts: [String]?
|
|
var rule: [String]?
|
|
var regex: [String]?
|
|
var filter: [String]?
|
|
var script: [String]?
|
|
}
|
|
}
|