diff --git a/Sources/SwiftDevRant/AuthToken.swift b/Sources/SwiftDevRant/AuthToken.swift deleted file mode 100644 index 793cf65..0000000 --- a/Sources/SwiftDevRant/AuthToken.swift +++ /dev/null @@ -1,10 +0,0 @@ -public struct AuthToken: Codable, Hashable { - public struct Container: Codable { - public let auth_token: AuthToken - } - - public let id: Int - public let key: String - public let expire_time: Int - public let user_id: Int -} diff --git a/Sources/SwiftDevRant/DevRantApiError.swift b/Sources/SwiftDevRant/DevRantApiError.swift new file mode 100644 index 0000000..f3d624e --- /dev/null +++ b/Sources/SwiftDevRant/DevRantApiError.swift @@ -0,0 +1,15 @@ +public struct DevRantApiError: Swift.Error { + let message: String +} + +public extension DevRantApiError { + struct CodingData: Decodable, Swift.Error { + let error: String + } +} + +public extension DevRantApiError.CodingData { + var decoded: DevRantApiError { + .init(message: error) + } +} diff --git a/Sources/SwiftDevRant/Models/AuthToken.swift b/Sources/SwiftDevRant/Models/AuthToken.swift new file mode 100644 index 0000000..b3c4a64 --- /dev/null +++ b/Sources/SwiftDevRant/Models/AuthToken.swift @@ -0,0 +1,43 @@ +import Foundation + +public struct AuthToken: Hashable { + public let id: Int + public let key: String + public let expireTime: Date + public let userId: Int + + public init(id: Int, key: String, expireTime: Date, userId: Int) { + self.id = id + self.key = key + self.expireTime = expireTime + self.userId = userId + } + + public var isExpired: Bool { + expireTime < Date() + } +} + +extension AuthToken { + struct CodingData: Codable { + struct Container: Codable { + let auth_token: AuthToken.CodingData + } + + let id: Int + let key: String + let expire_time: Int + let user_id: Int + } +} + +extension AuthToken.CodingData { + var decoded: AuthToken { + .init( + id: id, + key: key, + expireTime: Date(timeIntervalSince1970: TimeInterval(expire_time)), + userId: user_id + ) + } +} diff --git a/Sources/SwiftDevRant/Request/Request.swift b/Sources/SwiftDevRant/Request/Request.swift index 6673c8c..8c3c87f 100644 --- a/Sources/SwiftDevRant/Request/Request.swift +++ b/Sources/SwiftDevRant/Request/Request.swift @@ -19,7 +19,7 @@ public struct Request { } } - public struct EmptyError: Decodable { + public struct EmptyError: Decodable, Swift.Error { } diff --git a/Sources/SwiftDevRant/SwiftDevRant.swift b/Sources/SwiftDevRant/SwiftDevRant.swift index 8292d50..0283c4e 100644 --- a/Sources/SwiftDevRant/SwiftDevRant.swift +++ b/Sources/SwiftDevRant/SwiftDevRant.swift @@ -1,3 +1,85 @@ public struct SwiftDevRant { let request = Request(encoder: .devRant, decoder: .devRant) + let backend = DevRantBackend() + + func makeConfig(_ method: Request.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> Request.Config { + var urlParameters = urlParameters + urlParameters["app"] = "3" + if let token { + urlParameters["token_id"] = String(token.id) + urlParameters["token_key"] = token.key + urlParameters["user_id"] = String(token.userId) + } + + var headers = headers + headers["Content-Type"] = "application/x-www-form-urlencoded" + + return .init(method: method, backend: backend, path: path, urlParameters: urlParameters, headers: headers) + } + + public func logIn(username: String, password: String) async throws -> AuthToken { + var parameters: [String: String] = [:] + parameters["username"] = username + parameters["password"] = password + + let config = makeConfig(.post, path: "users/auth-token", urlParameters: parameters) + + let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + + return response.auth_token.decoded + } + + /// Gets a personalized feed of rants. + /// + /// - Parameters: + /// - token: The token from the `logIn` call response. + /// - limit: The number of rants to get for pagination. + /// - skip: How many rants to skip for pagination. + /// - sessionHash: Pass the session hash value from the last rant feed response or `nil` if calling for the first time. + public func getRantFeed(token: AuthToken, sort: RantFeed.Sort = .algorithm, limit: Int = 20, skip: Int, sessionHash: String?) async throws -> RantFeed { + var parameters: [String: String] = [:] + + parameters["sort"] = switch sort { + case .algorithm: "algo" + case .recent: "recent" + case .top: "top" + } + + switch sort { + case .top(range: let range): + parameters["range"] = switch range { + case .day: "day" + case .week: "week" + case .month: "month" + case .all: "all" + } + default: + break + } + + parameters["limit"] = String(limit) + parameters["skip"] = String(skip) + parameters["prev_set"] = sessionHash + + parameters["plat"] = "1" // I don't know wtf that is. + parameters["nari"] = "1" // I don't know wtf that is. + + let config = makeConfig(.get, path: "devrant/rants", urlParameters: parameters) + + let response: RantFeed.CodingData = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + + return response.decoded + } + + /// Get all weeklies as a list. + /// + /// - Parameters: + /// - token: The token from the `logIn` call response. + public func getWeeklies(token: AuthToken) async throws -> [Weekly] { + let config = makeConfig(.get, path: "devrant/weekly-list") + + let response: Weekly.CodingData.List = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + + return response.weeks.map(\.decoded) + } }