Skip to content

Commit

Permalink
Merge pull request #96 from makinosp/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
makinosp authored Oct 27, 2024
2 parents 3148e5b + fab9dbe commit b4f4550
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 33 deletions.
47 changes: 47 additions & 0 deletions Sources/VRCKit/Models/User/UserModel+Encodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// UserModel+Encodable.swift
// VRCKit
//
// Created by makinosp on 2024/10/27.
//

import Foundation

extension User: Encodable {
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: UserCodingKeys.self)
try container.encode(activeFriends, forKey: .activeFriends)
try container.encode(allowAvatarCopying, forKey: .allowAvatarCopying)
try container.encodeIfPresent(bio, forKey: .bio)
try container.encode(bioLinks.wrappedValue, forKey: .bioLinks)
try container.encode(currentAvatar, forKey: .currentAvatar)
try container.encodeIfPresent(avatarImageUrl, forKey: .currentAvatarImageUrl)
try container.encodeIfPresent(avatarThumbnailUrl, forKey: .currentAvatarThumbnailImageUrl)
if let dateJoined = dateJoined {
let dateJoinedString = DateFormatter.dateStringFormat.string(from: dateJoined)
try container.encode(dateJoinedString, forKey: .dateJoined)
}
try container.encode(displayName, forKey: .displayName)
try container.encode(friendKey, forKey: .friendKey)
try container.encode(friends, forKey: .friends)
try container.encode(homeLocation, forKey: .homeLocation)
try container.encode(id, forKey: .id)
try container.encode(isFriend, forKey: .isFriend)
try container.encode(lastActivity, forKey: .lastActivity)
try container.encode(lastLogin, forKey: .lastLogin)
try container.encode(lastPlatform, forKey: .lastPlatform)
try container.encode(offlineFriends, forKey: .offlineFriends)
try container.encode(onlineFriends, forKey: .onlineFriends)
try container.encode(pastDisplayNames, forKey: .pastDisplayNames)
try container.encodeIfPresent(profilePicOverride, forKey: .profilePicOverride)
try container.encode(state, forKey: .state)
try container.encode(status, forKey: .status)
try container.encode(statusDescription, forKey: .statusDescription)
try container.encode(tags, forKey: .tags)
try container.encode(twoFactorAuthEnabled, forKey: .twoFactorAuthEnabled)
try container.encodeIfPresent(userIcon, forKey: .userIcon)
try container.encodeIfPresent(userLanguage, forKey: .userLanguage)
try container.encodeIfPresent(userLanguageCode, forKey: .userLanguageCode)
try container.encode(presence, forKey: .presence)
}
}
20 changes: 20 additions & 0 deletions Sources/VRCKit/Models/User/UserModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ public struct User: Sendable, ProfileDetailRepresentable {
}
}

extension User: Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.hashValue == rhs.hashValue
}
}

extension User: RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else { return nil }
guard let decoded: User = try? Serializer.shared.decode(data) else { return nil }
self = decoded
}

public var rawValue: String {
guard let data = try? Serializer.shared.encode(self) else { return "" }
guard let encoded = String(data: data, encoding: .utf8) else { return "" }
return encoded
}
}

public extension User {
var platform: UserPlatform { presence.platform }
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/VRCKit/Models/World/InstanceModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ public struct Instance: Sendable, Identifiable, Hashable, Decodable {

@MemberwiseInit(.public)
public struct Platforms: Sendable, Hashable, Codable {
public let android: Int
public let ios: Int
public let standalonewindows: Int
@Init(default: 0) public let android: Int
@Init(default: 0) public let ios: Int
@Init(default: 0) public let standalonewindows: Int
}

public enum GroupAccessType: String, Sendable, Codable {
case `public`, plus
}

public enum Region: String, Sendable, Codable {
public enum Region: String, Sendable, Codable, CaseIterable {
case us, use, eu, jp, unknown
}

Expand Down
12 changes: 6 additions & 6 deletions Sources/VRCKit/Services/AuthenticationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public final actor AuthenticationService: APIService, AuthenticationServiceProto
let path = "\(authPath)/exists"
let queryItems = [URLQueryItem(name: "username", value: userId.description)]
let response = try await client.request(path: path, method: .get, queryItems: queryItems)
let result: ExistsResponse = try await Serializer.shared.decode(response.data)
let result: ExistsResponse = try Serializer.shared.decode(response.data)
return result.userExists
}

Expand All @@ -30,10 +30,10 @@ public final actor AuthenticationService: APIService, AuthenticationServiceProto
let path = "\(authPath)/user"
let response = try await client.request(path: path, method: .get, basic: true)
do {
let user: User = try await Serializer.shared.decode(response.data)
let user: User = try Serializer.shared.decode(response.data)
return .left(user)
} catch _ as DecodingError {
let result: RequiresTwoFactorAuthResponse = try await Serializer.shared.decode(response.data)
let result: RequiresTwoFactorAuthResponse = try Serializer.shared.decode(response.data)
guard let requires = result.requires else {
throw VRCKitError.unexpected
}
Expand All @@ -49,21 +49,21 @@ public final actor AuthenticationService: APIService, AuthenticationServiceProto
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 await Serializer.shared.encode(VerifyRequest(code: code))
let requestData = try Serializer.shared.encode(VerifyRequest(code: code))
let response = try await client.request(
path: path,
method: .post,
body: requestData
)
let result: VerifyResponse = try await Serializer.shared.decode(response.data)
let result: VerifyResponse = try Serializer.shared.decode(response.data)
return result.verified
}

/// Verifies the user's authentication token.
/// - Returns: A boolean indicating if the token is valid.
public func verifyAuthToken() async throws -> Bool {
let response = try await client.request(path: authPath, method: .get)
let result: VerifyAuthTokenResponse = try await Serializer.shared.decode(response.data)
let result: VerifyAuthTokenResponse = try Serializer.shared.decode(response.data)
return result.ok
}

Expand Down
12 changes: 6 additions & 6 deletions Sources/VRCKit/Services/FavoriteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol {
public func listFavoriteGroups() async throws -> [FavoriteGroup] {
let path = "favorite/groups"
let response = try await client.request(path: path, method: .get)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Lists a user's all favorites with the specified parameters.
Expand Down Expand Up @@ -63,7 +63,7 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol {
queryItems.append(URLQueryItem(name: "tag", value: tag.description))
}
let response = try await client.request(path: path, method: .get, queryItems: queryItems)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Fetches details of favorite groups asynchronously.
Expand Down Expand Up @@ -100,11 +100,11 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol {
tag: String
) async throws -> Favorite {
let path = "favorites"
let requestData = try await Serializer.shared.encode(
let requestData = try Serializer.shared.encode(
RequestToAddFavorite(type: type, favoriteId: favoriteId, tags: [tag])
)
let response = try await client.request(path: path, method: .post, body: requestData)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Updates a favorite group with the given parameters, display name, and visibility.
Expand All @@ -123,7 +123,7 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol {
let pathParams = ["favorite", "group", source.type.rawValue, source.name, source.ownerId]
let path = pathParams.joined(separator: "/")
let body = RequestToUpdateFavoriteGroup(displayName: displayName, visibility: visibility)
let requestData = try await Serializer.shared.encode(body)
let requestData = try Serializer.shared.encode(body)
_ = try await client.request(path: path, method: .put, body: requestData)
}

Expand All @@ -133,6 +133,6 @@ public final actor FavoriteService: APIService, FavoriteServiceProtocol {
public func removeFavorite(favoriteId: String) async throws -> SuccessResponse {
let path = "favorites/\(favoriteId)"
let response = try await client.request(path: path, method: .delete)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}
}
2 changes: 1 addition & 1 deletion Sources/VRCKit/Services/FriendService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public final actor FriendService: APIService, FriendServiceProtocol {
URLQueryItem(name: "offline", value: offline.description)
]
let response = try await client.request(path: path, method: .get, queryItems: queryItems)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// A helper function that splits a large API request tasks to fetch friend data concurrently,
Expand Down
4 changes: 2 additions & 2 deletions Sources/VRCKit/Services/InstanceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final actor InstanceService: APIService, InstanceServiceProtocol {
path: "\(path)/\(worldId):\(instanceId)",
method: .get
)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Fetches an instance using the specified location string.
Expand All @@ -32,6 +32,6 @@ public final actor InstanceService: APIService, InstanceServiceProtocol {
/// - Throws: An error if the request fails or the data cannot be decoded.
public func fetchInstance(location: String) async throws -> Instance {
let response = try await client.request(path: "\(path)/\(location)", method: .get)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}
}
4 changes: 2 additions & 2 deletions Sources/VRCKit/Services/UserNoteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final actor UserNoteService: APIService, UserNoteServiceProtocol {
let response = try await request(
userNote: UserNoteRequest(targetUserId: targetUserId, note: note)
)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Clears the note for a specific user by sending an empty note to the API.
Expand All @@ -37,7 +37,7 @@ public final actor UserNoteService: APIService, UserNoteServiceProtocol {
/// - Parameter userNote: The `UserNoteRequest` containing the user ID and note content.
/// - Returns: The `HTTPResponse` received from the API.
private func request(userNote: UserNoteRequest) async throws -> APIClient.HTTPResponse {
let requestData = try await Serializer.shared.encode(userNote)
let requestData = try Serializer.shared.encode(userNote)
return try await client.request(path: path, method: .post, body: requestData)
}
}
4 changes: 2 additions & 2 deletions Sources/VRCKit/Services/UserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public final actor UserService: APIService, UserServiceProtocol {
/// Fetch a user
public func fetchUser(userId: String) async throws -> UserDetail {
let response = try await client.request(path: "\(path)/\(userId)", method: .get)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

/// Update user
public func updateUser(id: String, editedInfo: EditableUserInfo) async throws {
let requestData = try await Serializer.shared.encode(editedInfo)
let requestData = try Serializer.shared.encode(editedInfo)
_ = try await client.request(
path: "\(path)/\(id)",
method: .put,
Expand Down
4 changes: 2 additions & 2 deletions Sources/VRCKit/Services/WorldService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public final actor WorldService: APIService, WorldServiceProtocol {

public func fetchWorld(worldId: String) async throws -> World {
let response = try await client.request(path: "\(path)/\(worldId)", method: .get)
return try await Serializer.shared.decode(response.data)
return try Serializer.shared.decode(response.data)
}

public func fetchFavoritedWorlds() async throws -> [FavoriteWorld] {
Expand All @@ -44,7 +44,7 @@ public final actor WorldService: APIService, WorldServiceProtocol {
URLQueryItem(name: "offset", value: offset.description)
]
let response = try await client.request(path: "\(path)/favorites", method: .get, queryItems: queryItems)
let favoriteWorldWrapper: SafeDecodingArray<FavoriteWorld> = try await Serializer.shared.decode(response.data)
let favoriteWorldWrapper: SafeDecodingArray<FavoriteWorld> = try Serializer.shared.decode(response.data)
return favoriteWorldWrapper.wrappedValue
}
}
11 changes: 4 additions & 7 deletions Sources/VRCKit/Utils/CookieManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@ import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
import MemberwiseInit

@MemberwiseInit
public final actor CookieManager {
private var domainURL: String?

init(domainURL: String) {
self.domainURL = domainURL
}
@Init(.internal) private var domainURL: String

/// Retrieves the cookies stored for the VRChat API domain.
/// - Returns: An array of `HTTPCookie` objects.
public var cookies: [HTTPCookie] {
guard let domainURL = domainURL,
let url = URL(string: domainURL),
guard let url = URL(string: domainURL),
let cookies = HTTPCookieStorage.shared.cookies(for: url) else { return [] }
return cookies
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/VRCKit/Utils/Serializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

final actor Serializer {
final class Serializer: Sendable {
static let shared = Serializer()
private let decoder: JSONDecoder
private let encoder: JSONEncoder
Expand Down

0 comments on commit b4f4550

Please sign in to comment.