203 lines
9.2 KiB
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
|
|
)
|
|
}
|
|
}
|