Add required MediaBrowser auth header and Jellyfin date format transcoder
This commit is contained in:
parent
3a16773092
commit
e6d44ac1ea
2 changed files with 70 additions and 17 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import Foundation
|
||||
import Adwaita
|
||||
import Foundation
|
||||
import LuminateCore
|
||||
|
||||
public struct ServerSetupView: View {
|
||||
|
|
@ -22,10 +22,12 @@ public struct ServerSetupView: View {
|
|||
icon: .custom(name: "dev.bscubed.Luminate"),
|
||||
description: "Enter your Jellyfin server details"
|
||||
) {
|
||||
VStack {
|
||||
EntryRow("Server URL", text: $serverURL)
|
||||
EntryRow("Username", text: $username)
|
||||
PasswordEntryRow("Password", text: $password)
|
||||
VStack(spacing: 16) {
|
||||
PreferencesGroup("Server configuration") {
|
||||
EntryRow("Server URL", text: $serverURL)
|
||||
EntryRow("Username", text: $username)
|
||||
PasswordEntryRow("Password", text: $password)
|
||||
}
|
||||
Button("Connect") {
|
||||
connect()
|
||||
}
|
||||
|
|
@ -44,7 +46,12 @@ public struct ServerSetupView: View {
|
|||
}
|
||||
|
||||
private func connect() {
|
||||
guard let url = URL(string: serverURL), !username.isEmpty else {
|
||||
var urlString = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") {
|
||||
urlString = "https://" + urlString
|
||||
}
|
||||
urlString = urlString.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
guard let url = URL(string: urlString), !username.isEmpty else {
|
||||
error = "Please enter a valid server URL and username"
|
||||
return
|
||||
}
|
||||
|
|
@ -55,15 +62,16 @@ public struct ServerSetupView: View {
|
|||
let client = JellyfinClient(serverURL: url)
|
||||
let result = try await client.authenticate(username: username, password: password)
|
||||
let userId = result.User?.value1.Id ?? ""
|
||||
await MainActor.run {
|
||||
isLoading = false
|
||||
onLogin(client, userId)
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
onLogin(client, userId)
|
||||
} catch (JellyfinError.httpError(let code)) {
|
||||
isLoading = false
|
||||
self.error = "Failed to login. HTTP code \(code)"
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
isLoading = false
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
isLoading = false
|
||||
self.error = error.localizedDescription
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,47 @@ import OpenAPIRuntime
|
|||
import OpenAPIURLSession
|
||||
import HTTPTypes
|
||||
|
||||
struct JellyfinDateTranscoder: DateTranscoder {
|
||||
func encode(_ date: Date) throws -> String {
|
||||
ISO8601DateFormatter().string(from: date)
|
||||
}
|
||||
|
||||
func decode(_ dateString: String) throws -> Date {
|
||||
let withoutExtraDigits = dateString.replacingOccurrences(
|
||||
of: #"\.(\d{3})\d+"#,
|
||||
with: ".$1",
|
||||
options: .regularExpression
|
||||
)
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let date = formatter.date(from: withoutExtraDigits) {
|
||||
return date
|
||||
}
|
||||
formatter.formatOptions = [.withInternetDateTime]
|
||||
if let date = formatter.date(from: withoutExtraDigits) {
|
||||
return date
|
||||
}
|
||||
throw DecodingError.dataCorrupted(
|
||||
.init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted: \(dateString)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private let clientName = "Luminate"
|
||||
private let deviceName = "Desktop"
|
||||
private let deviceId = "luminate-001"
|
||||
private let clientVersion = "1.0.0"
|
||||
|
||||
func mediaBrowserHeader(token: String? = nil) -> String {
|
||||
if let token {
|
||||
"MediaBrowser Token=\"\(token)\", Client=\"\(clientName)\", Device=\"\(deviceName)\", DeviceId=\"\(deviceId)\", Version=\"\(clientVersion)\""
|
||||
} else {
|
||||
"MediaBrowser Client=\"\(clientName)\", Device=\"\(deviceName)\", DeviceId=\"\(deviceId)\", Version=\"\(clientVersion)\""
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthMiddleware: ClientMiddleware {
|
||||
let token: String
|
||||
let token: String?
|
||||
|
||||
func intercept(
|
||||
_ request: HTTPRequest,
|
||||
|
|
@ -14,7 +53,7 @@ struct AuthMiddleware: ClientMiddleware {
|
|||
next: (HTTPRequest, OpenAPIRuntime.HTTPBody?, URL) async throws -> (HTTPResponse, OpenAPIRuntime.HTTPBody?)
|
||||
) async throws -> (HTTPResponse, OpenAPIRuntime.HTTPBody?) {
|
||||
var request = request
|
||||
request.headerFields[.authorization] = "MediaBrowser Token=\"\(token)\""
|
||||
request.headerFields[.authorization] = mediaBrowserHeader(token: token)
|
||||
return try await next(request, body, baseURL)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,18 +71,24 @@ public actor JellyfinClient {
|
|||
private var token: String?
|
||||
private var client: Client
|
||||
|
||||
private let clientConfig: Configuration
|
||||
|
||||
public init(serverURL: URL) {
|
||||
self.serverURL = serverURL
|
||||
self.token = nil
|
||||
self.clientConfig = Configuration(dateTranscoder: JellyfinDateTranscoder())
|
||||
self.client = Client(
|
||||
serverURL: serverURL,
|
||||
transport: URLSessionTransport()
|
||||
configuration: clientConfig,
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [AuthMiddleware(token: nil)]
|
||||
)
|
||||
}
|
||||
|
||||
private func makeClient(token: String) -> Client {
|
||||
Client(
|
||||
serverURL: serverURL,
|
||||
configuration: clientConfig,
|
||||
transport: URLSessionTransport(),
|
||||
middlewares: [AuthMiddleware(token: token)]
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue