666 lines
26 KiB
Swift
666 lines
26 KiB
Swift
import Foundation
|
|
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?
|
|
|
|
func intercept(
|
|
_ request: HTTPRequest,
|
|
body: OpenAPIRuntime.HTTPBody?,
|
|
baseURL: URL,
|
|
operationID: String,
|
|
next: (HTTPRequest, OpenAPIRuntime.HTTPBody?, URL) async throws -> (HTTPResponse, OpenAPIRuntime.HTTPBody?)
|
|
) async throws -> (HTTPResponse, OpenAPIRuntime.HTTPBody?) {
|
|
var request = request
|
|
request.headerFields[.authorization] = mediaBrowserHeader(token: token)
|
|
return try await next(request, body, baseURL)
|
|
}
|
|
}
|
|
|
|
public enum JellyfinError: Error {
|
|
case httpError(Int)
|
|
case notAuthenticated
|
|
case invalidResponse
|
|
case decodingError(String)
|
|
}
|
|
|
|
public actor JellyfinClient {
|
|
public let serverURL: URL
|
|
public private(set) var userId: String?
|
|
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,
|
|
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)]
|
|
)
|
|
}
|
|
|
|
public func authenticate(username: String, password: String) async throws -> Components.Schemas.AuthenticationResult {
|
|
let response = try await client.authenticateUserByName(Operations.AuthenticateUserByName.Input(
|
|
body: .json(.init(value1: .init(username: username, pw: password)))
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse }
|
|
token = accessToken
|
|
client = makeClient(token: accessToken)
|
|
userId = result.user?.value1.id
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse }
|
|
token = accessToken
|
|
client = makeClient(token: accessToken)
|
|
userId = result.user?.value1.id
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
guard let accessToken = result.accessToken else { throw JellyfinError.invalidResponse }
|
|
token = accessToken
|
|
client = makeClient(token: accessToken)
|
|
userId = result.user?.value1.id
|
|
return result
|
|
}
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getItems(
|
|
userId: String,
|
|
parentId: String? = nil,
|
|
includeItemTypes: [Components.Schemas.BaseItemKind]? = nil,
|
|
fields: [Components.Schemas.ItemFields]? = nil,
|
|
filters: [Components.Schemas.ItemFilter]? = nil,
|
|
sortBy: [Components.Schemas.ItemSortBy]? = nil,
|
|
sortOrder: [Components.Schemas.SortOrder]? = nil,
|
|
searchTerm: String? = nil,
|
|
startIndex: Int32? = nil,
|
|
limit: Int32? = nil,
|
|
recursive: Bool? = nil,
|
|
isFavorite: Bool? = nil
|
|
) async throws -> Components.Schemas.BaseItemDtoQueryResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetItems.Input.Query(userId: userId)
|
|
query.parentId = parentId
|
|
query.includeItemTypes = includeItemTypes
|
|
query.fields = fields
|
|
query.filters = filters
|
|
query.sortBy = sortBy
|
|
query.sortOrder = sortOrder
|
|
query.searchTerm = searchTerm
|
|
query.startIndex = startIndex
|
|
query.limit = limit
|
|
query.recursive = recursive
|
|
query.isFavorite = isFavorite
|
|
let response = try await client.getItems(Operations.GetItems.Input(query: query))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getItem(itemId: String, userId: String? = nil) async throws -> Components.Schemas.BaseItemDto {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.getItem(Operations.GetItem.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getUserViews(userId: String) async throws -> Components.Schemas.BaseItemDtoQueryResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.getUserViews(Operations.GetUserViews.Input(
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getNextUp(
|
|
userId: String,
|
|
startIndex: Int32? = nil,
|
|
limit: Int32? = nil,
|
|
fields: [Components.Schemas.ItemFields]? = nil,
|
|
seriesId: String? = nil,
|
|
parentId: String? = nil,
|
|
enableResumable: Bool? = nil,
|
|
enableRewatching: Bool? = nil
|
|
) async throws -> Components.Schemas.BaseItemDtoQueryResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetNextUp.Input.Query(userId: userId)
|
|
query.startIndex = startIndex
|
|
query.limit = limit
|
|
query.fields = fields
|
|
query.seriesId = seriesId
|
|
query.parentId = parentId
|
|
query.enableResumable = enableResumable
|
|
query.enableRewatching = enableRewatching
|
|
let response = try await client.getNextUp(Operations.GetNextUp.Input(query: query))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getSeasons(
|
|
seriesId: String,
|
|
userId: String,
|
|
fields: [Components.Schemas.ItemFields]? = nil,
|
|
isSpecialSeason: Bool? = nil,
|
|
enableImages: Bool? = nil,
|
|
imageTypeLimit: Int32? = nil,
|
|
enableImageTypes: [Components.Schemas.ImageType]? = nil,
|
|
enableUserData: Bool? = nil
|
|
) async throws -> Components.Schemas.BaseItemDtoQueryResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetSeasons.Input.Query(userId: userId)
|
|
query.fields = fields
|
|
query.isSpecialSeason = isSpecialSeason
|
|
query.enableImages = enableImages
|
|
query.imageTypeLimit = imageTypeLimit
|
|
query.enableImageTypes = enableImageTypes
|
|
query.enableUserData = enableUserData
|
|
let response = try await client.getSeasons(Operations.GetSeasons.Input(
|
|
path: .init(seriesId: seriesId),
|
|
query: query
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .notFound:
|
|
throw JellyfinError.httpError(404)
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getEpisodes(
|
|
seriesId: String,
|
|
userId: String,
|
|
seasonId: String? = nil,
|
|
season: Int32? = nil,
|
|
fields: [Components.Schemas.ItemFields]? = nil,
|
|
startIndex: Int32? = nil,
|
|
limit: Int32? = nil,
|
|
enableImages: Bool? = nil,
|
|
imageTypeLimit: Int32? = nil,
|
|
enableImageTypes: [Components.Schemas.ImageType]? = nil,
|
|
enableUserData: Bool? = nil
|
|
) async throws -> Components.Schemas.BaseItemDtoQueryResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetEpisodes.Input.Query(userId: userId)
|
|
query.seasonId = seasonId
|
|
query.season = season
|
|
query.fields = fields
|
|
query.startIndex = startIndex
|
|
query.limit = limit
|
|
query.enableImages = enableImages
|
|
query.imageTypeLimit = imageTypeLimit
|
|
query.enableImageTypes = enableImageTypes
|
|
query.enableUserData = enableUserData
|
|
let response = try await client.getEpisodes(Operations.GetEpisodes.Input(
|
|
path: .init(seriesId: seriesId),
|
|
query: query
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .notFound:
|
|
throw JellyfinError.httpError(404)
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getSearchHints(
|
|
searchTerm: String,
|
|
userId: String? = nil,
|
|
startIndex: Int32? = nil,
|
|
limit: Int32? = nil,
|
|
includeItemTypes: [Components.Schemas.BaseItemKind]? = nil,
|
|
parentId: String? = nil
|
|
) async throws -> Components.Schemas.SearchHintResult {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetSearchHints.Input.Query(searchTerm: searchTerm)
|
|
query.userId = userId
|
|
query.startIndex = startIndex
|
|
query.limit = limit
|
|
query.includeItemTypes = includeItemTypes
|
|
query.parentId = parentId
|
|
let response = try await client.getSearchHints(Operations.GetSearchHints.Input(query: query))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func markPlayedItem(itemId: String, userId: String, datePlayed: Date? = nil) async throws -> Components.Schemas.UserItemDataDto {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.markPlayedItem(Operations.MarkPlayedItem.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId, datePlayed: datePlayed)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .notFound:
|
|
throw JellyfinError.httpError(404)
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func markUnplayedItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.markUnplayedItem(Operations.MarkUnplayedItem.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .notFound:
|
|
throw JellyfinError.httpError(404)
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func markFavoriteItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.markFavoriteItem(Operations.MarkFavoriteItem.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func unmarkFavoriteItem(itemId: String, userId: String) async throws -> Components.Schemas.UserItemDataDto {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.unmarkFavoriteItem(Operations.UnmarkFavoriteItem.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getPlaybackInfo(itemId: String, userId: String) async throws -> Components.Schemas.PlaybackInfoResponse {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.getPlaybackInfo(Operations.GetPlaybackInfo.Input(
|
|
path: .init(itemId: itemId),
|
|
query: .init(userId: userId)
|
|
))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .notFound:
|
|
throw JellyfinError.httpError(404)
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func reportPlaybackStart(info: Components.Schemas.PlaybackStartInfo) async throws {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.reportPlaybackStart(Operations.ReportPlaybackStart.Input(
|
|
body: .json(.init(value1: info))
|
|
))
|
|
switch response {
|
|
case .noContent:
|
|
return
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func reportPlaybackProgress(info: Components.Schemas.PlaybackProgressInfo) async throws {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.reportPlaybackProgress(Operations.ReportPlaybackProgress.Input(
|
|
body: .json(.init(value1: info))
|
|
))
|
|
switch response {
|
|
case .noContent:
|
|
return
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func reportPlaybackStopped(info: Components.Schemas.PlaybackStopInfo) async throws {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
let response = try await client.reportPlaybackStopped(Operations.ReportPlaybackStopped.Input(
|
|
body: .json(.init(value1: info))
|
|
))
|
|
switch response {
|
|
case .noContent:
|
|
return
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func getLatestMedia(
|
|
userId: String,
|
|
parentId: String? = nil,
|
|
fields: [Components.Schemas.ItemFields]? = nil,
|
|
includeItemTypes: [Components.Schemas.BaseItemKind]? = nil,
|
|
limit: Int32? = nil,
|
|
enableImages: Bool? = nil,
|
|
imageTypeLimit: Int32? = nil,
|
|
enableImageTypes: [Components.Schemas.ImageType]? = nil,
|
|
enableUserData: Bool? = nil,
|
|
groupItems: Bool? = nil
|
|
) async throws -> [Components.Schemas.BaseItemDto] {
|
|
guard token != nil else { throw JellyfinError.notAuthenticated }
|
|
var query = Operations.GetLatestMedia.Input.Query(userId: userId)
|
|
query.parentId = parentId
|
|
query.fields = fields
|
|
query.includeItemTypes = includeItemTypes
|
|
query.limit = limit
|
|
query.enableImages = enableImages
|
|
query.imageTypeLimit = imageTypeLimit
|
|
query.enableImageTypes = enableImageTypes
|
|
query.enableUserData = enableUserData
|
|
query.groupItems = groupItems
|
|
let response = try await client.getLatestMedia(Operations.GetLatestMedia.Input(query: query))
|
|
switch response {
|
|
case .ok(let ok):
|
|
switch ok.body {
|
|
case .json(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_camelcase_quot_(let result):
|
|
return result
|
|
case .applicationJsonProfile_Quot_pascalcase_quot_(let result):
|
|
return result
|
|
}
|
|
case .unauthorized:
|
|
throw JellyfinError.httpError(401)
|
|
case .forbidden:
|
|
throw JellyfinError.httpError(403)
|
|
case .serviceUnavailable:
|
|
throw JellyfinError.httpError(503)
|
|
case .undocumented(let code, _):
|
|
throw JellyfinError.httpError(code)
|
|
}
|
|
}
|
|
|
|
public func imageURL(itemId: String, imageType: Components.Schemas.ImageType, tag: String? = nil, maxWidth: Int32? = nil, quality: Int32? = 90) -> URL? {
|
|
guard var components = URLComponents(url: serverURL, resolvingAgainstBaseURL: false) else { return nil }
|
|
components.path = "/Items/\(itemId)/Images/\(imageType)"
|
|
var queryItems: [URLQueryItem] = []
|
|
if let tag { queryItems.append(.init(name: "tag", value: tag)) }
|
|
if let maxWidth { queryItems.append(.init(name: "maxWidth", value: "\(maxWidth)")) }
|
|
if let quality { queryItems.append(.init(name: "quality", value: "\(quality)")) }
|
|
components.queryItems = queryItems.isEmpty ? nil : queryItems
|
|
return components.url
|
|
}
|
|
|
|
public func userImageURL(userId: String, tag: String? = nil) -> URL? {
|
|
guard var components = URLComponents(url: serverURL, resolvingAgainstBaseURL: false) else { return nil }
|
|
components.path = "/Users/\(userId)/Images/Primary"
|
|
if let tag { components.queryItems = [.init(name: "tag", value: tag)] }
|
|
return components.url
|
|
}
|
|
}
|