luminate/Sources/LuminateCore/Protocols/JellyfinService+Convenience.swift

203 lines
9.2 KiB
Swift

//
// JellyfinService+Convenience.swift
//
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
import Foundation
/// Shorter spellings of the ``JellyfinService`` requirements for the common cases.
///
/// A protocol requirement cannot declare default arguments, so the defaults live here instead as
/// reduced-arity overloads. Each one forwards to the full-arity requirement; because the arities
/// differ, the forward resolves to the requirement rather than recursing.
extension JellyfinService {
/// Fetches the signed-in user's libraries.
///
/// - Returns: The libraries, in server order.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func libraries() async throws -> [JellyfinLibrary] {
try await libraries(JellyfinListOptions())
}
/// Fetches the first page of items with no filters applied.
///
/// - Returns: One page of items plus the total match count.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func items() async throws -> JellyfinMediaPage {
try await items(JellyfinMediaQuery())
}
/// Fetches one item in full detail for the signed-in user.
///
/// - Parameter id: The item identifier. Must not be empty.
/// - Returns: The item.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `id` is empty, or
/// ``JellyfinClientError/notFound`` if no such item exists.
package func item(id: String) async throws -> JellyfinMediaItem {
try await item(id: id, userID: nil)
}
/// Fetches the signed-in user's Continue Watching row.
///
/// - Returns: One page of partially played items.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func resumeItems() async throws -> JellyfinMediaPage {
try await resumeItems(JellyfinListOptions())
}
/// Fetches the signed-in user's Next Up row across every series.
///
/// - Returns: One page of next-up episodes.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func nextUp() async throws -> JellyfinMediaPage {
try await nextUp(seriesID: nil, options: JellyfinListOptions())
}
/// Fetches the Next Up row across every series with explicit options.
///
/// - Parameter options: Honours `userID`, `parentID`, `startIndex`, `limit`, and `fields`.
/// - Returns: One page of next-up episodes.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func nextUp(options: JellyfinListOptions) async throws -> JellyfinMediaPage {
try await nextUp(seriesID: nil, options: options)
}
/// Fetches the signed-in user's Latest Media row.
///
/// - Returns: The most recently added items, newest first.
/// - Throws: ``JellyfinClientError/unauthorized`` if the token is missing or rejected.
package func latestMedia() async throws -> [JellyfinMediaItem] {
try await latestMedia(JellyfinListOptions())
}
/// Fetches the seasons of a series for the signed-in user.
///
/// - Parameter seriesID: The series identifier. Must not be empty.
/// - Returns: The seasons, in broadcast order.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `seriesID` is empty, or
/// ``JellyfinClientError/notFound`` if no such series exists.
package func seasons(seriesID: String) async throws -> JellyfinMediaPage {
try await seasons(seriesID: seriesID, options: JellyfinListOptions())
}
/// Fetches every episode of a series for the signed-in user.
///
/// - Parameter seriesID: The series identifier. Must not be empty.
/// - Returns: One page of episodes, in broadcast order.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `seriesID` is empty, or
/// ``JellyfinClientError/notFound`` if no such series exists.
package func episodes(seriesID: String) async throws -> JellyfinMediaPage {
try await episodes(seriesID: seriesID, options: JellyfinListOptions())
}
/// Runs a search-as-you-type query with the server's default result limit.
///
/// - Parameter term: The search text. Must not be empty or whitespace only.
/// - Returns: The matching hints, best match first.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `term` is blank.
package func searchHints(term: String) async throws -> [JellyfinSearchHint] {
try await searchHints(term: term, options: JellyfinListOptions())
}
/// Downloads an item's first image of a given type at the server's default rendition.
///
/// - Parameters:
/// - itemID: The item that owns the artwork. Must not be empty.
/// - type: Which artwork slot to fetch.
/// - Returns: The encoded image bytes.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if the item has no image in that slot.
package func image(itemID: String, type: JellyfinImageType) async throws -> Data {
try await image(itemID: itemID, type: type, index: nil, request: JellyfinImageRequest())
}
/// Downloads an item's first image of a given type at a specific rendition.
///
/// - Parameters:
/// - itemID: The item that owns the artwork. Must not be empty.
/// - type: Which artwork slot to fetch.
/// - request: The rendition to ask the server for.
/// - Returns: The encoded image bytes.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if the item has no image in that slot.
package func image(
itemID: String,
type: JellyfinImageType,
request: JellyfinImageRequest
) async throws -> Data {
try await image(itemID: itemID, type: type, index: nil, request: request)
}
/// Marks an item watched by the signed-in user, as of now.
///
/// - Parameter itemID: The item to mark. Must not be empty.
/// - Returns: The item's updated playback state.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if no such item exists.
package func markPlayed(itemID: String) async throws -> JellyfinUserData {
try await markPlayed(itemID: itemID, userID: nil, datePlayed: nil)
}
/// Marks an item unwatched by the signed-in user.
///
/// - Parameter itemID: The item to mark. Must not be empty.
/// - Returns: The item's updated playback state.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if no such item exists.
package func markUnplayed(itemID: String) async throws -> JellyfinUserData {
try await markUnplayed(itemID: itemID, userID: nil)
}
/// Adds an item to the signed-in user's favourites.
///
/// - Parameter itemID: The item to favourite. Must not be empty.
/// - Returns: The item's updated playback state.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if no such item exists.
package func markFavorite(itemID: String) async throws -> JellyfinUserData {
try await markFavorite(itemID: itemID, userID: nil)
}
/// Removes an item from the signed-in user's favourites.
///
/// - Parameter itemID: The item to unfavourite. Must not be empty.
/// - Returns: The item's updated playback state.
/// - Throws: ``JellyfinClientError/invalidArgument(name:)`` if `itemID` is empty, or
/// ``JellyfinClientError/notFound`` if no such item exists.
package func unmarkFavorite(itemID: String) async throws -> JellyfinUserData {
try await unmarkFavorite(itemID: itemID, userID: nil)
}
/// Changes the signed-in account's password.
///
/// - Parameters:
/// - currentPassword: The existing password.
/// - newPassword: The replacement password.
/// - Throws: ``JellyfinClientError/unauthorized`` if the current password is wrong, or
/// ``JellyfinClientError/forbidden`` if the account may not change its own password.
package func updateUserPassword(currentPassword: String?, newPassword: String?) async throws {
try await updateUserPassword(
userID: nil,
currentPassword: currentPassword,
currentPIN: nil,
newPassword: newPassword,
resetPassword: nil
)
}
}