// // JellyfinService+Convenience.swift // // Copyright 2026 Brendan Szymanski // // 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 . // // 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 ) } }