From 2bcdc2de87abb6e52562512edcc7cdab14e85329 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 21 Apr 2024 22:34:12 +0800 Subject: [PATCH 1/9] EnkaAPI // +MiHoMoQueriedProfile. --- .../MiHoMoSupport/MiHoMoQueriedProfile.swift | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedProfile.swift diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedProfile.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedProfile.swift new file mode 100644 index 00000000..3c46be35 --- /dev/null +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedProfile.swift @@ -0,0 +1,174 @@ +// (c) 2023 and onwards Pizza Studio (GPL v3.0 License). +// ==================== +// This code is released under the GPL v3.0 License (SPDX-License-Identifier: GPL-3.0) + +// MARK: - MiHoMo + +public enum MiHoMo { + public struct QueriedProfile: Codable, Hashable { + public let player: PlayerInfo + public let characters: [CharacterInfo] + } +} + +// MARK: - Structs + +extension MiHoMo.QueriedProfile { + public struct LevelInfo: Codable, Hashable, Identifiable { + public let id: String + public let level: Int + } + + public struct AvatarInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let icon: String + } + + public struct PathInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let icon: String + } + + public struct ElementInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let color: String + public let icon: String + } + + public struct SkillInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let level: Int + public let maxLevel: Int + public let element: ElementInfo? + public let type: String + public let typeText: String + public let effect: String + public let effectText: String + public let simpleDesc: String + public let desc: String + public let icon: String + } + + public struct SkillTreeInfo: Codable, Hashable, Identifiable { + public let id: String + public let level: Int + public let anchor: String + public let maxLevel: Int + public let icon: String + public let parent: String? + } + + public struct AttributeInfo: Codable, Hashable { + public let field: String + public let name: String + public let icon: String + public let value: Float + public let display: String + public let percent: Bool + } + + public struct PropertyInfo: Codable, Hashable { + public let type: String + public let field: String + public let name: String + public let icon: String + public let value: Float + public let display: String + public let percent: Bool + } + + public struct SubAffixInfo: Codable, Hashable { + public let count: Int + public let step: Int + public let type: String + public let field: String + public let name: String + public let icon: String + public let value: Float + public let display: String + public let percent: Bool + } + + public struct RelicInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let setId: String + public let setName: String + public let rarity: Int + public let level: Int + public let icon: String + public let mainAffix: PropertyInfo? + public let subAffix: [SubAffixInfo] + } + + public struct RelicSetInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let icon: String + public let num: Int + public let desc: String + public let properties: [PropertyInfo] + } + + public struct LightConeInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let rarity: Int + public let rank: Int + public let level: Int + public let promotion: Int + public let icon: String + public let preview: String + public let portrait: String + public let path: PathInfo? + public let attributes: [AttributeInfo] + public let properties: [PropertyInfo] + } + + public struct SpaceInfo: Codable, Hashable { + // public let memoryData: MemoryInfo // Deprecated. + public let universeLevel: Int + public let lightConeCount: Int + public let avatarCount: Int + public let achievementCount: Int + } + + public struct PlayerInfo: Codable, Hashable { + public let uid: String + public let nickname: String + public let level: Int + public let worldLevel: Int + public let friendCount: Int + public let avatar: AvatarInfo? + public let signature: String + public let isDisplay: Bool + public let spaceInfo: SpaceInfo? + } + + public struct CharacterInfo: Codable, Hashable, Identifiable { + public let id: String + public let name: String + public let rarity: Int + public let rank: Int + public let level: Int + public let promotion: Int + public let icon: String + public let preview: String + public let portrait: String + public let rankIcons: [String] + public let path: PathInfo? + public let element: ElementInfo? + public let skills: [SkillInfo] + public let skillTrees: [SkillTreeInfo] + public let lightCone: LightConeInfo? + public let relics: [RelicInfo] + public let relicSets: [RelicSetInfo] + public let attributes: [AttributeInfo] + public let additions: [AttributeInfo] + public let properties: [PropertyInfo] + } +} From 45c51234c33b4da7939969cd2dd2e1df28d37294 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Sun, 21 Apr 2024 23:15:13 +0800 Subject: [PATCH 2/9] EnkaAPI // MiHoMoQueriedProfile: + fetchability. --- .../DataSummarySupport/AvatarSummarized.swift | 8 ++-- .../QueriedProfile_Summarizer.swift | 2 +- .../ModelsAndImpls/QueriedProfile.swift | 28 +++++++------- .../MiHoMoQueriedImpl/MiHoMoRequest.swift | 37 +++++++++++++++++++ .../Tests/HBEnkaAPITests/HBEnkaAPITests.swift | 2 +- .../Tests/HBEnkaAPITests/MiHoMoAPITests.swift | 29 +++++++++++++++ 6 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedImpl/MiHoMoRequest.swift create mode 100644 Packages/HBEnkaAPI/Tests/HBEnkaAPITests/MiHoMoAPITests.swift diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift index ef4a8ddd..cfcdc6fb 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift @@ -240,12 +240,12 @@ extension EnkaHSR.AvatarSummarized { self.localizedName = theDB.locTable[nameHash] ?? "EnkaId: \(fetched.tid)" self.trainedLevel = fetched.level self.refinement = fetched.rank - self.basicProps = fetched.flat.props.compactMap { currentRecord in + self.basicProps = fetched.flat?.props.compactMap { currentRecord in if let theType = EnkaHSR.PropertyType(rawValue: currentRecord.type) { return PropertyPair(theDB: theDB, type: theType, value: currentRecord.value) } return nil - } + } ?? [] // TODO: 目前先忽略那些没有图标的词条,回头单独再订做一套图标。 self.specialProps = theDB.meta.equipmentSkill.query( id: enkaId, stage: fetched.rank @@ -295,12 +295,12 @@ extension EnkaHSR.AvatarSummarized { self.enkaId = fetched.tid self.commonInfo = theCommonInfo self.paramDataFetched = fetched - let props: [PropertyPair] = fetched.flat.props.compactMap { currentRecord in + let props: [PropertyPair] = fetched.flat?.props.compactMap { currentRecord in if let theType = EnkaHSR.PropertyType(rawValue: currentRecord.type) { return PropertyPair(theDB: theDB, type: theType, value: currentRecord.value, isArtifact: true) } return nil - } + } ?? [] guard let theMainProp = props.first else { return nil } self.mainProp = theMainProp self.subProps = Array(props.dropFirst()) diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift index 20566f5a..b7fafdb3 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift @@ -82,7 +82,7 @@ extension EnkaHSR.QueryRelated.DetailInfo.Avatar { let artifactSetProps: [EnkaHSR.AvatarSummarized.PropertyPair] = { var resultPairs = [EnkaHSR.AvatarSummarized.PropertyPair]() var setIDCounters: [Int: Int] = [:] - artifactsInfo.map(\.paramDataFetched.flat.setID).forEach { setIDCounters[$0, default: 0] += 1 } + artifactsInfo.compactMap(\.paramDataFetched.flat?.setID).forEach { setIDCounters[$0, default: 0] += 1 } setIDCounters.forEach { setId, count in guard count >= 2 else { return } let x = theDB.meta.relic.setSkill.query(id: setId, stage: 2).map { diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift index 391a0fdf..29483e86 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift @@ -7,7 +7,7 @@ extension EnkaHSR.QueryRelated { public struct QueriedProfile: Codable, Hashable { public let detailInfo: DetailInfo? - public let uid: String + public let uid: String? public let message: String? } @@ -45,19 +45,19 @@ extension EnkaHSR.QueryRelated { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.uid = try container.decode(Int.self, forKey: .uid) - self.nickname = try container.decode(String?.self, forKey: .nickname) ?? "@Nanashibito" - self.level = try container.decode(Int?.self, forKey: .level) ?? 0 - self.friendCount = try container.decode(Int?.self, forKey: .friendCount) ?? 0 - self.signature = try container.decode(String?.self, forKey: .signature) ?? "" - self.recordInfo = try container.decode(RecordInfo?.self, forKey: .recordInfo) - self.headIcon = try container.decode(Int?.self, forKey: .headIcon) ?? 1310 - self.worldLevel = try container.decode(Int?.self, forKey: .worldLevel) ?? 0 - self.isDisplayAvatar = try container.decode(Bool?.self, forKey: .isDisplayAvatar) ?? false - self.avatarDetailList = try container.decode([Avatar]?.self, forKey: .avatarDetailList) ?? [] + self.nickname = (try? container.decode(String.self, forKey: .nickname)) ?? "@Nanashibito" + self.level = (try? container.decode(Int.self, forKey: .level)) ?? 0 + self.friendCount = (try? container.decode(Int.self, forKey: .friendCount)) ?? 0 + self.signature = (try? container.decode(String.self, forKey: .signature)) ?? "" + self.recordInfo = try? container.decode(RecordInfo.self, forKey: .recordInfo) + self.headIcon = (try? container.decode(Int.self, forKey: .headIcon)) ?? 1310 + self.worldLevel = (try? container.decode(Int.self, forKey: .worldLevel)) ?? 0 + self.isDisplayAvatar = (try? container.decode(Bool.self, forKey: .isDisplayAvatar)) ?? false + self.avatarDetailList = (try? container.decode([Avatar].self, forKey: .avatarDetailList)) ?? [] do { - self.platform = .init(rawValue: try container.decode(Int?.self, forKey: .platform) ?? 0) ?? .editor + self.platform = .init(rawValue: (try container.decode(Int?.self, forKey: .platform)) ?? 0) ?? .editor } catch { - self.platform = .init(string: try container.decode(String?.self, forKey: .platform) ?? "EDITOR") + self.platform = .init(string: (try? container.decode(String?.self, forKey: .platform)) ?? "EDITOR") } } @@ -108,7 +108,7 @@ extension EnkaHSR.QueryRelated.DetailInfo { public let rank, level, tid: Int public let promotion: Int? - public let flat: EquipmentFlat + public let flat: EquipmentFlat? // Promotion, guarded public var promotionRank: Int { @@ -163,7 +163,7 @@ extension EnkaHSR.QueryRelated.DetailInfo { public let level: Int? public let subAffixList: [SubAffixItem] public let mainAffixId, tid: Int - public let flat: ArtifactItem.Flat + public let flat: ArtifactItem.Flat? public let exp: Int? // MARK: Internal diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedImpl/MiHoMoRequest.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedImpl/MiHoMoRequest.swift new file mode 100644 index 00000000..b52f2e04 --- /dev/null +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/MiHoMoSupport/MiHoMoQueriedImpl/MiHoMoRequest.swift @@ -0,0 +1,37 @@ +// (c) 2023 and onwards Pizza Studio (GPL v3.0 License). +// ==================== +// This code is released under the GPL v3.0 License (SPDX-License-Identifier: GPL-3.0) + +import Foundation + +extension MiHoMo.QueriedProfile { + public static func fetch(lang: String = "en", uid: String) async throws -> Self { + let langTag = lang.isEmpty ? "" : "&lang=\(lang)" + let urlLink = "https://api.mihomo.me/sr_info_parsed/\(uid)?version=v2\(langTag)" + // swiftlint:disable force_unwrapping + let url = URL(string: urlLink)! + // swiftlint:enable force_unwrapping + let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url)) + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let requestResult = try decoder.decode( + Self.self, + from: data + ) + return requestResult + } + + public static func fetchEnka(uid: String) async throws -> EnkaHSR.QueryRelated.QueriedProfile { + let urlLink = "https://api.mihomo.me/sr_info/\(uid)" + // swiftlint:disable force_unwrapping + let url = URL(string: urlLink)! + // swiftlint:enable force_unwrapping + let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url)) + let decoder = JSONDecoder() + let requestResult = try decoder.decode( + EnkaHSR.QueryRelated.QueriedProfile.self, + from: data + ) + return requestResult + } +} diff --git a/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/HBEnkaAPITests.swift b/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/HBEnkaAPITests.swift index 21db153e..0b2aabe1 100644 --- a/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/HBEnkaAPITests.swift +++ b/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/HBEnkaAPITests.swift @@ -43,7 +43,7 @@ final class HBEnkaAPITests: XCTestCase { var profile: EnkaHSR.QueryRelated.QueriedProfile? do { let obj = try JSONDecoder().decode(EnkaHSR.QueryRelated.QueriedProfile.self, from: jsonData) - uid = obj.uid + uid = obj.uid ?? obj.detailInfo?.uid.description ?? 114514810.description profile = obj } catch { throw (error) diff --git a/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/MiHoMoAPITests.swift b/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/MiHoMoAPITests.swift new file mode 100644 index 00000000..0ec47ad8 --- /dev/null +++ b/Packages/HBEnkaAPI/Tests/HBEnkaAPITests/MiHoMoAPITests.swift @@ -0,0 +1,29 @@ +// (c) 2023 and onwards Pizza Studio (GPL v3.0 License). +// ==================== +// This code is released under the GPL v3.0 License (SPDX-License-Identifier: GPL-3.0) + +import Defaults +@testable import HBEnkaAPI +import XCTest + +// MARK: - MiHoMoAPITests + +final class MiHoMoAPITests: XCTestCase { + func testFetchingMiHoMoProfile() async throws { + do { + let dbObj = try await MiHoMo.QueriedProfile.fetch(uid: "114514810") + print(dbObj.player.uid) + } catch { + throw (error) + } + } + + func testFetchingEnkaProfile() async throws { + do { + let dbObj = try await MiHoMo.QueriedProfile.fetchEnka(uid: "114514810") + print(dbObj.detailInfo?.uid ?? 114_514) + } catch { + throw (error) + } + } +} From 9b2213349bc88ef7e78f67d62bf732ac9a8a0c4d Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 01:27:59 +0800 Subject: [PATCH 3/9] EnkaAPI // Fix a compiler issue with ResIcon(). --- .../SwiftUIImpl/SwiftUIImpl_Pizza.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Packages/HBEnkaAPI/Sources/EnkaSwiftUIViews/SwiftUIImpl/SwiftUIImpl_Pizza.swift b/Packages/HBEnkaAPI/Sources/EnkaSwiftUIViews/SwiftUIImpl/SwiftUIImpl_Pizza.swift index 2895953f..d1b95328 100644 --- a/Packages/HBEnkaAPI/Sources/EnkaSwiftUIViews/SwiftUIImpl/SwiftUIImpl_Pizza.swift +++ b/Packages/HBEnkaAPI/Sources/EnkaSwiftUIViews/SwiftUIImpl/SwiftUIImpl_Pizza.swift @@ -27,6 +27,17 @@ public struct ResIcon: View { public let imageHandler: (Image) -> Image public var body: some View { + #if os(OSX) + if let image = NSImage(contentsOfFile: path) { + imageHandler(Image(nsImage: image)) + } else { + AsyncImage(url: .init(fileURLWithPath: path)) { image in + imageHandler(image) + } placeholder: { + placeholder() + } + } + #else if let image = UIImage(contentsOfFile: path) { imageHandler(Image(uiImage: image)) } else { @@ -36,6 +47,7 @@ public struct ResIcon: View { placeholder() } } + #endif } } From 1540fc641d3fe4018a21a0cd22221e1291e3701f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 01:29:39 +0800 Subject: [PATCH 4/9] Summarizer // Attempt to calculate flats using MiHoMo Origin data. --- .../DataSummarySupport/AvatarSummarized.swift | 12 ++-- .../QueriedProfile_Summarizer.swift | 68 +++++++++++++++---- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift index cfcdc6fb..7975a2eb 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/AvatarSummarized.swift @@ -240,12 +240,12 @@ extension EnkaHSR.AvatarSummarized { self.localizedName = theDB.locTable[nameHash] ?? "EnkaId: \(fetched.tid)" self.trainedLevel = fetched.level self.refinement = fetched.rank - self.basicProps = fetched.flat?.props.compactMap { currentRecord in + self.basicProps = fetched.getFlat(theDB: theDB).props.compactMap { currentRecord in if let theType = EnkaHSR.PropertyType(rawValue: currentRecord.type) { return PropertyPair(theDB: theDB, type: theType, value: currentRecord.value) } return nil - } ?? [] + } // TODO: 目前先忽略那些没有图标的词条,回头单独再订做一套图标。 self.specialProps = theDB.meta.equipmentSkill.query( id: enkaId, stage: fetched.rank @@ -295,21 +295,25 @@ extension EnkaHSR.AvatarSummarized { self.enkaId = fetched.tid self.commonInfo = theCommonInfo self.paramDataFetched = fetched - let props: [PropertyPair] = fetched.flat?.props.compactMap { currentRecord in + guard let flat = fetched.getFlat(theDB: theDB) else { return nil } + let props: [PropertyPair] = flat.props.compactMap { currentRecord in if let theType = EnkaHSR.PropertyType(rawValue: currentRecord.type) { return PropertyPair(theDB: theDB, type: theType, value: currentRecord.value, isArtifact: true) } return nil - } ?? [] + } guard let theMainProp = props.first else { return nil } self.mainProp = theMainProp self.subProps = Array(props.dropFirst()) + self.setID = flat.setID } // MARK: Public /// Unique Artifact ID, defining its Rarity, Set Suite, and Body Part. public let enkaId: Int + /// Artifact Set ID. + public let setID: Int /// Common information fetched from EnkaDB. public let commonInfo: EnkaHSR.DBModels.Artifact /// Data from Enka query result profile. diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift index b7fafdb3..f27ad523 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/DataSummarySupport/Implementations/QueriedProfile_Summarizer.swift @@ -46,14 +46,10 @@ extension EnkaHSR.QueryRelated.DetailInfo.Avatar { // Panel: Base Props from the Weapon. - let baseMetaWeapon = theDB.meta.equipment[equipment.tid.description]?[equipment.promotionRank.description] - guard let baseMetaWeapon = baseMetaWeapon else { return nil } - panel.maxHP += baseMetaWeapon.baseHP - panel.attack += baseMetaWeapon.baseAttack - panel.defence += baseMetaWeapon.baseDefence - panel.maxHP += baseMetaWeapon.hpAdd * Double(equipInfo.trainedLevel - 1) - panel.attack += baseMetaWeapon.attackAdd * Double(equipInfo.trainedLevel - 1) - panel.defence += baseMetaWeapon.defenceAdd * Double(equipInfo.trainedLevel - 1) + let equipFlat = equipment.getFlat(theDB: theDB) + panel.maxHP += equipFlat.props.first { EnkaHSR.PropertyType(rawValue: $0.type) == .baseHP }?.value ?? 0 + panel.attack += equipFlat.props.first { EnkaHSR.PropertyType(rawValue: $0.type) == .baseAttack }?.value ?? 0 + panel.defence += equipFlat.props.first { EnkaHSR.PropertyType(rawValue: $0.type) == .baseDefence }?.value ?? 0 // Panel: Handle all additional props @@ -61,8 +57,7 @@ extension EnkaHSR.QueryRelated.DetailInfo.Avatar { let weaponSpecialProps: [EnkaHSR.AvatarSummarized.PropertyPair] = equipInfo.specialProps - // Panel: 来自天赋树的面板加成。 - // English: Base and Additional Props from the Skill Tree. + // Panel: Base and Additional Props from the Skill Tree. let skillTreeProps: [EnkaHSR.AvatarSummarized.PropertyPair] = skillTreeList.compactMap { currentNode in if currentNode.level == 1 { @@ -82,7 +77,7 @@ extension EnkaHSR.QueryRelated.DetailInfo.Avatar { let artifactSetProps: [EnkaHSR.AvatarSummarized.PropertyPair] = { var resultPairs = [EnkaHSR.AvatarSummarized.PropertyPair]() var setIDCounters: [Int: Int] = [:] - artifactsInfo.compactMap(\.paramDataFetched.flat?.setID).forEach { setIDCounters[$0, default: 0] += 1 } + artifactsInfo.map(\.setID).forEach { setIDCounters[$0, default: 0] += 1 } setIDCounters.forEach { setId, count in guard count >= 2 else { return } let x = theDB.meta.relic.setSkill.query(id: setId, stage: 2).map { @@ -103,7 +98,7 @@ extension EnkaHSR.QueryRelated.DetailInfo.Avatar { let allProps = skillTreeProps + weaponSpecialProps + artifactProps + artifactSetProps panel.triageAndHandle(theDB: theDB, allProps, element: mainInfo.element) - // Panel: 将最终面板转成输出物件要用到的格式。 + // Panel: Final Output. let propPair = panel.converted(theDB: theDB, element: mainInfo.element) @@ -239,3 +234,52 @@ extension EnkaHSR.AvatarSummarized.PropertyPair { } } } + +extension EnkaHSR.QueryRelated.DetailInfo.ArtifactItem { + public func getFlat(theDB: EnkaHSR.EnkaDB) -> EnkaHSR.QueryRelated.DetailInfo.ArtifactItem.Flat? { + var result = [EnkaHSR.QueryRelated.DetailInfo.Prop]() + guard let matchedArtifact = theDB.artifacts[tid.description] else { return nil } + let mainAffix = theDB.meta.relic.mainAffix[ + matchedArtifact.mainAffixGroup.description + ]?[mainAffixId.description] + if let mainAffix = mainAffix { + result.append( + .init( + type: mainAffix.property.rawValue, + value: mainAffix.baseValue + mainAffix.levelAdd * Double(level ?? 0) + ) + ) + } + subAffixList.forEach { sub in + guard let subAffix = theDB.meta.relic.subAffix[ + matchedArtifact.subAffixGroup.description + ]?[sub.affixId.description] else { return } + result.append( + .init( + type: subAffix.property.rawValue, + value: subAffix.baseValue * Double(sub.cnt) + subAffix.stepValue * Double(sub.step ?? 0) + ) + ) + } + return .init( + props: result, + setName: matchedArtifact.setID, + setID: matchedArtifact.setID + ) + } +} + +extension EnkaHSR.QueryRelated.DetailInfo.Equipment { + public func getFlat(theDB: EnkaHSR.EnkaDB) -> EnkaHSR.QueryRelated.DetailInfo.EquipmentFlat { + var result = [EnkaHSR.QueryRelated.DetailInfo.Prop]() + if let table = theDB.meta.equipment[tid.description]?[(promotion ?? 0).description] { + let summedHP = table.baseHP + table.hpAdd * (Double(level) - 1) + let summedATK = table.baseAttack + table.attackAdd * (Double(level) - 1) + let summedDEF = table.baseDefence + table.defenceAdd * (Double(level) - 1) + result.append(.init(type: EnkaHSR.PropertyType.baseHP.rawValue, value: summedHP)) + result.append(.init(type: EnkaHSR.PropertyType.baseAttack.rawValue, value: summedATK)) + result.append(.init(type: EnkaHSR.PropertyType.baseDefence.rawValue, value: summedDEF)) + } + return .init(props: result, name: theDB.weapons[tid.description]?.equipmentName.hash ?? 0) + } +} From bb4f6e22a374586e63af92b2eda65c251d65dc23 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 01:42:48 +0800 Subject: [PATCH 5/9] QueriedProfile // Remove Mihomo-Incompliant properties. --- .../EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift index 29483e86..161e1a98 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift @@ -107,8 +107,8 @@ extension EnkaHSR.QueryRelated.DetailInfo { // MARK: Public public let rank, level, tid: Int + // public let flat: EquipmentFlat? // UNAVAILABLE_IN_MIHOMO_ORIGIN_RESULTS. public let promotion: Int? - public let flat: EquipmentFlat? // Promotion, guarded public var promotionRank: Int { @@ -122,7 +122,7 @@ extension EnkaHSR.QueryRelated.DetailInfo { case level case tid case promotion - case flat = "_flat" + // case flat = "_flat" // UNAVAILABLE_IN_MIHOMO_ORIGIN_RESULTS. } } @@ -163,7 +163,7 @@ extension EnkaHSR.QueryRelated.DetailInfo { public let level: Int? public let subAffixList: [SubAffixItem] public let mainAffixId, tid: Int - public let flat: ArtifactItem.Flat? + // public let flat: ArtifactItem.Flat? // UNAVAILABLE_IN_MIHOMO_ORIGIN_RESULTS. public let exp: Int? // MARK: Internal @@ -174,7 +174,7 @@ extension EnkaHSR.QueryRelated.DetailInfo { case subAffixList case mainAffixId case tid - case flat = "_flat" + // case flat = "_flat" // UNAVAILABLE_IN_MIHOMO_ORIGIN_RESULTS. case exp } } From 8e8af1837c63f7f1de3b4051c27a8771ac5b8c10 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 02:09:44 +0800 Subject: [PATCH 6/9] QueriedProfile // Merge assistAvatarList on decoding. --- .../ModelsAndImpls/QueriedProfile.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift index 161e1a98..cd4d62c5 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/ModelsAndImpls/QueriedProfile.swift @@ -40,6 +40,7 @@ extension EnkaHSR.QueryRelated { self.isDisplayAvatar = isDisplayAvatar self.platform = platform self.avatarDetailList = avatarDetailList + self.assistAvatarList = [] } public init(from decoder: Decoder) throws { @@ -53,7 +54,13 @@ extension EnkaHSR.QueryRelated { self.headIcon = (try? container.decode(Int.self, forKey: .headIcon)) ?? 1310 self.worldLevel = (try? container.decode(Int.self, forKey: .worldLevel)) ?? 0 self.isDisplayAvatar = (try? container.decode(Bool.self, forKey: .isDisplayAvatar)) ?? false - self.avatarDetailList = (try? container.decode([Avatar].self, forKey: .avatarDetailList)) ?? [] + // 在这个阶段就将 assistAvatarList 的内容并入到 avatarDetailList 内。 + let avatarListPrimary = (try? container.decode([Avatar].self, forKey: .assistAvatarList)) ?? [] + var avatarListSecondary = (try? container.decode([Avatar].self, forKey: .avatarDetailList)) ?? [] + let filteredCharIDs = avatarListPrimary.map(\.avatarId) + avatarListSecondary.removeAll { filteredCharIDs.contains($0.avatarId) } + self.assistAvatarList = [] + self.avatarDetailList = avatarListPrimary + avatarListSecondary do { self.platform = .init(rawValue: (try container.decode(Int?.self, forKey: .platform)) ?? 0) ?? .editor } catch { @@ -72,7 +79,7 @@ extension EnkaHSR.QueryRelated { public let isDisplayAvatar: Bool public let platform: PlatformType public let avatarDetailList: [Avatar] - // public let assistAvatarList: [Avatar] + public let assistAvatarList: [Avatar] } } From 8a98e1fc9542a3657166cba991f56b50e277f08f Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 01:54:05 +0800 Subject: [PATCH 7/9] HBMihoyoAPI // Add a UID-matched initializer for Server enum. --- .../HBMihoyoAPI/Common/Models/Server.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Packages/HBMihoyoAPI/Sources/HBMihoyoAPI/Common/Models/Server.swift b/Packages/HBMihoyoAPI/Sources/HBMihoyoAPI/Common/Models/Server.swift index 3131e1c8..a0d81323 100644 --- a/Packages/HBMihoyoAPI/Sources/HBMihoyoAPI/Common/Models/Server.swift +++ b/Packages/HBMihoyoAPI/Sources/HBMihoyoAPI/Common/Models/Server.swift @@ -17,6 +17,25 @@ public enum Server: String, CaseIterable { case europe = "prod_official_eur" case asia = "prod_official_asia" case hongKongMacauTaiwan = "prod_official_cht" + + // MARK: Lifecycle + + public init?(uid: String?) { + guard var theUID = uid else { return nil } + while theUID.count > 9 { + theUID = theUID.dropFirst().description + } + guard let initial = theUID.first, let initialInt = Int(initial.description) else { return nil } + switch initialInt { + case 1 ... 4: self = .china + case 5: self = .bilibili + case 6: self = .unitedStates + case 7: self = .europe + case 8: self = .asia + case 9: self = .hongKongMacauTaiwan + default: return nil + } + } } extension Server { From eb946c2b6dc021d6d7c48e8a039c14dd28e47b91 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 01:57:41 +0800 Subject: [PATCH 8/9] EnkaAPI // Query from MiHoMo instead for Celestia and Irminsul UIDs. --- .../EnkaQueryRelated/Sputnik/Sputnik.swift | 3 ++- .../Sources/HBEnkaAPI/HBEnkaAPI.swift | 25 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/Sputnik/Sputnik.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/Sputnik/Sputnik.swift index 06bac049..1fd50a24 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/Sputnik/Sputnik.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/EnkaQueryRelated/Sputnik/Sputnik.swift @@ -131,7 +131,8 @@ extension EnkaHSR.Sputnik { ) throw EnkaHSR.QueryRelated.Exception.refreshTooFast(dateWhenRefreshable: date) } else { - let enkaOfficial = EnkaHSR.HostType.profileQueryURLHeader + uid + let server = EnkaHSR.HostType(uid: uid) + let enkaOfficial = server.profileQueryURLHeader + uid // swiftlint:disable force_unwrapping let url = URL(string: enkaOfficial)! // swiftlint:enable force_unwrapping diff --git a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/HBEnkaAPI.swift b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/HBEnkaAPI.swift index 7294de47..67420664 100644 --- a/Packages/HBEnkaAPI/Sources/HBEnkaAPI/HBEnkaAPI.swift +++ b/Packages/HBEnkaAPI/Sources/HBEnkaAPI/HBEnkaAPI.swift @@ -119,13 +119,25 @@ extension EnkaHSR { case mainlandChina = 0 case enkaGlobal = 1 - // MARK: Public + // MARK: Lifecycle - public static var profileQueryURLHeader: String { - // MicroGG 目前不支持星穹铁道的资料查询。 - "https://enka.network/api/hsr/uid/" + public init(uid: String) { + var theUID = uid + while theUID.count > 9 { + theUID = theUID.dropFirst().description + } + guard let initial = theUID.first, let initialInt = Int(initial.description) else { + self = .enkaGlobal + return + } + switch initialInt { + case 1 ... 5: self = .mainlandChina + default: self = .enkaGlobal + } } + // MARK: Public + public var enkaDBSourceURLHeader: String { switch self { case .mainlandChina: return "https://gitcode.net/SHIKISUEN/Enka-API-docs/-/raw/master/" @@ -134,7 +146,10 @@ extension EnkaHSR { } public var profileQueryURLHeader: String { - Self.profileQueryURLHeader + switch self { + case .mainlandChina: return "https://api.mihomo.me/sr_info/" + case .enkaGlobal: return "https://enka.network/api/hsr/uid/" + } } public func viceVersa() -> Self { From 6f2dc5d3a6a36ac3facc7ea6c39a04ebb4477e61 Mon Sep 17 00:00:00 2001 From: ShikiSuen Date: Mon, 22 Apr 2024 02:00:35 +0800 Subject: [PATCH 9/9] ThanksView // Credit MiHoMo API service. --- Features/System/View/ThanksView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Features/System/View/ThanksView.swift b/Features/System/View/ThanksView.swift index 8a9fb40e..f312114e 100644 --- a/Features/System/View/ThanksView.swift +++ b/Features/System/View/ThanksView.swift @@ -20,6 +20,7 @@ struct ThanksView: View { Text(verbatim: "SwifterSwift\nhttps://github.com/SwifterSwift/SwifterSwift") Text(verbatim: "Defaults - Sindre Sorhus\nhttps://github.com/sindresorhus/Defaults") Text(verbatim: "Enka API - Enka Network\nhttps://enka.network/?hsr") + Text(verbatim: "MiHoMo Origin API Mirror - MiHoMo\nhttps://github.com/Mar-7th/March7th-Docs") } .font(.caption) Divider()