Files
Suwayomi-Server/server/src/main/kotlin/suwayomi/tachidesk/opds/OpdsAPI.kt
Zeedif 98576c6e62 feat(opds): Add option to skip metadata feed for direct access (#1879)
* feat(opds): add option to skip chapter metadata feed

Introduces a new server configuration `server.opdsSkipChapterMetadataFeed` (default: false).

When enabled, the OPDS chapter feed generates direct acquisition (CBZ download) and streaming (OPDS-PSE) links within the chapter list entries, bypassing the intermediate metadata subsection. This streamlines the user experience and improves compatibility with OPDS clients like KOReader that rely on direct links for automated downloading features.

* fix: lint

* fix(opds): enrich chapter data and refine sync logic for skip-metadata mode

Refines the `opdsSkipChapterMetadataFeed` implementation to ensure necessary data is available for direct links and handles synchronization logic appropriate for a list view.

- **Refactor ChapterForDownload:** Extract `refreshChapterPageList` and `updateChapterPersistence` to allow reusing page count verification logic outside the download flow.
- **Enrich Chapter Repository:** When skipping metadata, asynchronously verify page counts and calculate CBZ file sizes for chapters in the list. This ensures direct stream/download links are valid even if the chapter wasn't previously fully indexed.
- **KoSync Logic:** Implement synchronization logic in `OpdsEntryBuilder`. Since the user cannot be prompted in the chapter list view, `PROMPT` conflicts are explicitly ignored (prioritizing local progress), while updates are applied if non-conflicting.
- **OPDS Attributes:** Add `length` (file size) to acquisition links and ensure download links only appear for actually downloaded chapters.
- **Documentation:** Update `server.conf` description to clarify KoSync behavior in this mode.

* feat(download): improve chapter download filenames

* feat(opds): append language to source names

* feat(opds): handle empty chapter titles

* fix import org.jetbrains.exposed.v1.core.inList

* refactor(opds): reorganize API routes and update facet count calculations based on active filters

- **API Routing & Controllers**: Reorganize OPDS v1.2 route paths into logical groups in `OpdsAPI`. Centralize request filter extraction into `OpdsMangaFilter.fromContext`.
- **Facet Counting**: Extract `Query.applyOpdsMangaFilter` to apply active filters to facet and navigation queries. Pass the active filters to `NavigationRepository` and `MangaRepository` count queries (using `excludeField` to calculate sibling counts). This ensures category, source, language, status, and genre counts (`thr:count`) are accurately computed based on active selections.
- **Pagination**: Add pagination support to computed navigation feeds in `NavigationRepository` ( statuses and content languages).
- **Builders**: Standardize parameter ordering in `FeedBuilderInternal` and `OpdsEntryBuilder` constructors. Simplify pagination and facet link URL generation.

* fix(opds): remove redundant filter logic to avoid duplicate HAVING clauses

Resolve IllegalStateException crash caused by applying content filters twice in MangaRepository. Filtering is now handled exclusively by `applyOpdsMangaFilter`, allowing `applyMangaLibrarySort` to focus solely on ordering operations.

* revert(download): restore original CBZ filename scheme

* refactor(opds): simplify persistence updates and clean up chapter mapping

- Simplify page count and download checks in ChapterForDownload
- Clean up enriched chapter mapping in ChapterRepository to improve readability

* fix(opds): retrieve chapter archive size without leaving stream open

* perf(opds): avoid redundant DB query when refreshing chapter page list
2026-06-15 14:26:16 -04:00

90 lines
3.1 KiB
Kotlin

package suwayomi.tachidesk.opds
import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.path
import suwayomi.tachidesk.opds.controller.OpdsV1Controller
object OpdsAPI {
fun defineEndpoints() {
path("opds/v1.2") {
// OPDS Catalog Root Feed (Navigation)
get(OpdsV1Controller.rootFeed)
// OPDS Search Description Feed
get("search", OpdsV1Controller.searchFeed)
// Reading History Acquisition Feed
get("history", OpdsV1Controller.historyFeed)
// Library Updates Acquisition Feed
get("library-updates", OpdsV1Controller.libraryUpdatesFeed)
// --- Remote Catalog Exploration ---
// List of available online sources
get("explore", OpdsV1Controller.exploreSourcesFeed)
// Browse series from a specific online source
path("explore/source/{sourceId}") {
get(OpdsV1Controller.exploreSourceFeed)
}
// --- Library Navigation Feeds ---
path("library") {
// All Series in Library / Search Results Feed (Acquisition)
get("series", OpdsV1Controller.seriesFeed)
// Library Sources Navigation Feed
get("sources", OpdsV1Controller.librarySourcesFeed)
// Library Categories Navigation Feed
get("categories", OpdsV1Controller.categoriesFeed)
// Library Genres Navigation Feed
get("genres", OpdsV1Controller.genresFeed)
// Library Status Navigation Feed
get("statuses", OpdsV1Controller.statusesFeed)
// Library Content Languages Navigation Feed
get("languages", OpdsV1Controller.languagesFeed)
}
// --- Library Series Filters ---
// Source-Specific Series Acquisition Feed (Library)
path("source/{sourceId}") {
get(OpdsV1Controller.librarySourceFeed)
}
// Category-Specific Series Acquisition Feed (Library)
path("category/{categoryId}") {
get(OpdsV1Controller.categoryFeed)
}
// Genre-Specific Series Acquisition Feed (Library)
path("genre/{genre}") {
get(OpdsV1Controller.genreFeed)
}
// Status-Specific Series Acquisition Feed (Library)
path("status/{statusId}") {
get(OpdsV1Controller.statusMangaFeed)
}
// Language-Specific Series Acquisition Feed (Library)
path("language/{langCode}") {
get(OpdsV1Controller.languageFeed)
}
// --- Item Specific Feeds ---
// Series Chapters Acquisition Feed
path("series/{seriesId}/chapters") {
get(OpdsV1Controller.seriesChaptersFeed)
}
// Chapter Metadata Acquisition Feed
path("series/{seriesId}/chapter/{chapterIndex}/metadata") {
get(OpdsV1Controller.chapterMetadataFeed)
}
}
}
}