diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index c5f5e15..0000000 --- a/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftlintplugins", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", - "state" : { - "revision" : "6c3d6c32a37224179dc290f21e03d1238f3d963b", - "version" : "0.56.2" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift index 1eac926..642b52a 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,7 @@ import PackageDescription let package = Package( name: "VRCKit", + defaultLocalization: "en", platforms: [ .macOS(.v12), .iOS(.v15), @@ -12,23 +13,14 @@ let package = Package( .watchOS(.v6) ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "VRCKit", targets: ["VRCKit"] ) ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "0.55.1") - ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "VRCKit", - dependencies: [], path: "Sources" ), .testTarget( @@ -37,12 +29,3 @@ let package = Package( ) ] ) - -package.targets.forEach { - $0.plugins = [ - .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins") - ] - $0.swiftSettings = [ - .enableUpcomingFeature("ForwardTrailingClosures") // SE-0286 - ] -} diff --git a/Sources/VRCKit/APIClient.swift b/Sources/VRCKit/APIClient.swift index 5297dff..c3b4f1e 100644 --- a/Sources/VRCKit/APIClient.swift +++ b/Sources/VRCKit/APIClient.swift @@ -6,6 +6,9 @@ // import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public final class APIClient { typealias HTTPResponse = (data: Data, response: HTTPURLResponse) @@ -44,7 +47,7 @@ public final class APIClient { /// - password: The password associated with the username. /// - Returns: A Basic Authentication token string. /// - Throws: `VRCKitError.unexpectedError` if the username and password cannot be converted to UTF-8 data. - func encodeAuthorization(_ username: String, _ password: String) throws -> String { + private func encodeAuthorization(_ username: String, _ password: String) throws -> String { let authString = "\(username):\(password)" guard let payload = authString.data(using: .utf8) else { throw VRCKitError.unexpected @@ -95,12 +98,41 @@ public final class APIClient { request.httpBody = body } + #if canImport(FoundationNetworking) + return try await requestWithFoundationNetworking(request) + #else + return try await requestWithFoundation(request) + #endif + } + + #if canImport(FoundationNetworking) + private func requestWithFoundationNetworking(_ request: URLRequest) async throws -> HTTPResponse { + var requestError: Error? + let httpResponse = await withCheckedContinuation { continuation in + URLSession.shared.dataTask(with: request) { data, urlResponse, error in + guard let data = data, let reponse = urlResponse as? HTTPURLResponse else { + requestError = error + return + } + continuation.resume(returning: (data, reponse)) + } + .resume() + } + if let requestError = requestError { + throw requestError + } + return httpResponse + } + + #else + private func requestWithFoundation(_ request: URLRequest) async throws -> HTTPResponse { let (data, response) = try await URLSession.shared.data(for: request) guard let response = response as? HTTPURLResponse else { throw VRCKitError.invalidResponse } return (data, response) } + #endif } extension APIClient.Method: CustomStringConvertible { diff --git a/Sources/VRCKit/Errors.swift b/Sources/VRCKit/Errors.swift index 2214867..66894ec 100644 --- a/Sources/VRCKit/Errors.swift +++ b/Sources/VRCKit/Errors.swift @@ -15,6 +15,9 @@ public enum VRCKitError: Error, LocalizedError, Equatable { /// Represents an error from the API with details. case apiError(_ details: String) + /// Represents a bad gatewaty error. + case badGateway + /// Represents an error indicating that the client has been deallocated. case clientDeallocated @@ -37,6 +40,7 @@ public enum VRCKitError: Error, LocalizedError, Equatable { public var errorDescription: String? { switch self { case .apiError: "API Error" + case .badGateway: "Bad Gateway" case .clientDeallocated: "Client Deallocated" case .invalidResponse: "Invalid Response" case .invalidRequest: "Invalid Request" diff --git a/Sources/VRCKit/Models/EditableUserModel.swift b/Sources/VRCKit/Models/EditableUserModel.swift index ca61e9f..952cdce 100644 --- a/Sources/VRCKit/Models/EditableUserModel.swift +++ b/Sources/VRCKit/Models/EditableUserModel.swift @@ -24,8 +24,3 @@ public extension EditableUserInfo { tags = detail.tags } } - -public struct UpdatedUser: Codable { - public let bio: String? - public let statusDescription: String? -} diff --git a/Sources/VRCKit/Utils/TrustRank.swift b/Sources/VRCKit/Models/TrustRankModel.swift similarity index 94% rename from Sources/VRCKit/Utils/TrustRank.swift rename to Sources/VRCKit/Models/TrustRankModel.swift index 88c45e1..50cefd6 100644 --- a/Sources/VRCKit/Utils/TrustRank.swift +++ b/Sources/VRCKit/Models/TrustRankModel.swift @@ -1,11 +1,11 @@ // -// TrustRank.swift +// TrustRankModel.swift // VRCKit // // Created by makinosp on 2024/08/04. // -public enum TrustRank { +public enum TrustRank: Equatable { case trusted, known, user, newUser, visitor, unknown } diff --git a/Sources/VRCKit/Models/UserModel.swift b/Sources/VRCKit/Models/UserModel.swift index c2a5f57..f4dc629 100644 --- a/Sources/VRCKit/Models/UserModel.swift +++ b/Sources/VRCKit/Models/UserModel.swift @@ -38,26 +38,13 @@ public struct User: ProfileDetailRepresentable { public let userLanguage: String? public let userLanguageCode: String? public let presence: Presence -} - -public extension User { - var platform: UserPlatform { - presence.platform - } - var url: URL? { - URL(string: [Const.homeBaseUrl, "user", id].joined(separator: "/")) - } -} -public extension User { - struct DisplayName: Codable, Hashable { + public struct DisplayName: Codable, Hashable { public let displayName: String public let updatedAt: Date } -} -public extension User { - enum State: String, Codable { + public enum State: String, Codable { /// User is online in VRChat case online /// User is online, but not in VRChat @@ -65,10 +52,8 @@ public extension User { /// User is offline case offline } -} -public extension User { - struct Presence: Codable, Hashable { + public struct Presence: Codable, Hashable { public let groups: [String] public let id: String public let instance: String @@ -81,6 +66,15 @@ public extension User { } } +public extension User { + var platform: UserPlatform { + presence.platform + } + var url: URL? { + URL(string: [Const.homeBaseUrl, "user", id].joined(separator: "/")) + } +} + extension User.Presence { init() { groups = [] diff --git a/Sources/VRCKit/Models/UserTagModel.swift b/Sources/VRCKit/Models/UserTagModel.swift index 85a4b89..60214722 100644 --- a/Sources/VRCKit/Models/UserTagModel.swift +++ b/Sources/VRCKit/Models/UserTagModel.swift @@ -49,6 +49,8 @@ extension UserTags: Encodable { public func encode(to encoder: any Encoder) throws { var container = encoder.unkeyedContainer() let tags = languageTags.map(\.rawValue) - try container.encode(tags) + for tag in tags { + try container.encode(tag) + } } } diff --git a/Sources/VRCKit/Models/WorldModel.swift b/Sources/VRCKit/Models/WorldModel.swift index eba49f0..bd2fa32 100644 --- a/Sources/VRCKit/Models/WorldModel.swift +++ b/Sources/VRCKit/Models/WorldModel.swift @@ -10,7 +10,7 @@ import Foundation public struct World: Codable, Identifiable, Hashable { public let id: String public let name: String - public let description: String + public let description: String? public let featured: Bool public let authorId: String public let authorName: String @@ -30,9 +30,7 @@ public struct World: Codable, Identifiable, Hashable { public let visits: Int public let popularity: Int public let heat: Int - // MARK: Only world info params - // public let publicOccupants: Int - // public let privateOccupants: Int + public let favoriteGroup: String? public let version: Int? public enum ReleaseStatus: String, Codable { diff --git a/Sources/VRCKit/PreviewServices/PreviewDataProvider.swift b/Sources/VRCKit/PreviewServices/PreviewDataProvider.swift index 891c93c..e6b29d5 100644 --- a/Sources/VRCKit/PreviewServices/PreviewDataProvider.swift +++ b/Sources/VRCKit/PreviewServices/PreviewDataProvider.swift @@ -227,6 +227,7 @@ public final class PreviewDataProvider { visits: 1, popularity: 1, heat: 1, + favoriteGroup: "", version: 1 ) } diff --git a/Sources/VRCKit/Protocols/WorldServiceProtocol.swift b/Sources/VRCKit/Protocols/WorldServiceProtocol.swift index 5ee519c..5b59158 100644 --- a/Sources/VRCKit/Protocols/WorldServiceProtocol.swift +++ b/Sources/VRCKit/Protocols/WorldServiceProtocol.swift @@ -5,7 +5,6 @@ // Created by kiripoipoi on 2024/09/07. // -// WorldServiceProtocol.swift public protocol WorldServiceProtocol { func fetchWorld(worldId: String) async throws -> World func fetchFavoritedWorlds() async throws -> [World] diff --git a/Sources/VRCKit/Services/AuthenticationService.swift b/Sources/VRCKit/Services/AuthenticationService.swift index e2c4b6a..398b2db 100644 --- a/Sources/VRCKit/Services/AuthenticationService.swift +++ b/Sources/VRCKit/Services/AuthenticationService.swift @@ -37,6 +37,7 @@ public class AuthenticationService: APIService, AuthenticationServiceProtocol { /// Verify 2FA With TOTP or Email OTP public func verify2FA(verifyType: VerifyType, code: String) async throws -> Bool { + guard code.count == 6 else { throw VRCKitError.invalidRequest("Code must be 6 digits") } let path = "\(authPath)/twofactorauth/\(verifyType.rawValue.lowercased())/verify" let requestData = try Serializer.shared.encode(VerifyRequest(code: code)) let response = try await client.request( diff --git a/Sources/VRCKit/Services/WorldService.swift b/Sources/VRCKit/Services/WorldService.swift index 1349c88..9a93c90 100644 --- a/Sources/VRCKit/Services/WorldService.swift +++ b/Sources/VRCKit/Services/WorldService.swift @@ -14,8 +14,7 @@ public class WorldService: APIService, WorldServiceProtocol { } public func fetchFavoritedWorlds() async throws -> [World] { - let path = "worlds/favorites" - let response = try await client.request(path: path, method: .get) + let response = try await client.request(path: "\(path)/favorites", method: .get) let favoriteWorldWrapper: FavoriteWorldWrapper = try Serializer.shared.decode(response.data) return favoriteWorldWrapper.worlds } diff --git a/Sources/VRCKit/Utils/CookieManager.swift b/Sources/VRCKit/Utils/CookieManager.swift index a089a23..e134886 100644 --- a/Sources/VRCKit/Utils/CookieManager.swift +++ b/Sources/VRCKit/Utils/CookieManager.swift @@ -6,6 +6,9 @@ // import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif public final class CookieManager { private var domainURL: String? diff --git a/Sources/VRCKit/Utils/OptionalISO8601Date.swift b/Sources/VRCKit/Utils/OptionalISO8601Date.swift index c99ed6f..ee93369 100644 --- a/Sources/VRCKit/Utils/OptionalISO8601Date.swift +++ b/Sources/VRCKit/Utils/OptionalISO8601Date.swift @@ -48,11 +48,13 @@ extension OptionalISO8601Date: Encodable { } } -extension OptionalISO8601Date: Hashable { +extension OptionalISO8601Date: Equatable { public static func == (lhs: OptionalISO8601Date, rhs: OptionalISO8601Date) -> Bool { lhs.date == rhs.date } +} +extension OptionalISO8601Date: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(date) } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..28f1929 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + build: + image: swift:5.10.1 + command: bash -c "swift build -c release" + volumes: + - ${PWD}:${PWD} + working_dir: ${PWD} + lint: + image: ghcr.io/realm/swiftlint:latest + volumes: + - ${PWD}:${PWD} + working_dir: ${PWD}