No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
bivashy cbdc5a2ede
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
Decouple MediaMetadata and VideoRequestPayoad
2026-09-05 04:50:58 +05:00
.mvn [Feature] CI/CD for deploying to Maven repository (#2) 2026-09-04 18:34:25 +00:00
kodik-api-core Decouple MediaMetadata and VideoRequestPayoad 2026-09-05 04:50:58 +05:00
kodik-api-retrofit Set naming strategy to snake_case 2026-09-05 03:58:21 +05:00
.gitignore Implement /list endpoint, split into submodules core and retrofit 2026-09-03 16:37:30 +05:00
.woodpecker.yml [Feature] CI/CD for deploying to Maven repository (#2) 2026-09-04 18:34:25 +00:00
LICENSE Use LGPL-3.0 license 2026-09-03 16:43:30 +05:00
mvnw [Feature] CI/CD for deploying to Maven repository (#2) 2026-09-04 18:34:25 +00:00
mvnw.cmd [Feature] CI/CD for deploying to Maven repository (#2) 2026-09-04 18:34:25 +00:00
pom.xml [Feature] CI/CD for deploying to Maven repository (#2) 2026-09-04 18:34:25 +00:00
README.md Decouple MediaMetadata and VideoRequestPayoad 2026-09-05 04:50:58 +05:00

Kodik API

Typed Java client for the Kodik search, list and player links APIs.

kodik-api is a small, transport-agnostic library for the Kodik media API. It covers the /search and /list endpoints plus the parsing of the embedded player page: media metadata and the direct video links behind a player link. Request and response models are immutable records with fluent, generated builders, so every searchable media type, MPAA rating and anime kind is compile-time checked instead of being a magic string.

Installing / Getting started

The project is a Maven multi-module reactor. The core module contains the request/response models and the transport SPI; the optional Retrofit2 module provides a ready-made implementation of that SPI.

Add the core dependency to your pom.xml:

<dependency>
    <groupId>dev.bivashy</groupId>
    <artifactId>kodik-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

If you want the Retrofit2 transport, add it as well:

<dependency>
    <groupId>dev.bivashy</groupId>
    <artifactId>kodik-api-retrofit</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

A minimal "hello world" looks like this:

KodikHttpClient transport = RetrofitKodikHttpClientWrapper.create("https://kodik-api.com");
KodikAPI api = new KodikAPI(transport, "your-token");

Result<SearchResponse> found = api.search(
        api.search().byTitle("Naruto").build());

if (found instanceof Result.Success<?> success) {
    SearchResponse response = (SearchResponse) success.value();
    response.results().forEach(media -> System.out.println(media.title()));
}

Executing this sends a search request to Kodik, parses the JSON response into a typed SearchResponse, and prints the matching titles.

When you run inside Quarkus with the EasyRetrofit extension, do not pass the URL manually: inject the RetrofitKodikHttpClient that EasyRetrofit produces and configure the base URL in application.properties instead:

kodik.baseUrl=https://kodik-api.com

Initial Configuration

  • A Kodik API token. Kodik issues tokens per domain; you need one to authenticate.
  • Java 25 (the build targets maven.compiler.release 25).

Developing

Clone and build the reactor:

git clone git@git.bivashy.dev:anyame/kodik-api.git
cd kodik-api
mvn clean install

This compiles both modules, runs the annotation processor that generates the record builders (SearchRequestBuilder, ListRequestBuilder, SearchResponseBuilder, ListResponseBuilder), and installs the artifacts into your local Maven repository so dependent projects (like your Quarkus application) can resolve them.

Building

After code changes, re-run from the root:

mvn clean install

Deploying / Publishing

Publishing is handled by a Woodpecker CI workflow (.woodpecker.yml) that runs ./mvnw deploy -DskipTests on every push to main (and manually) and uploads the artifacts to the Forgejo Maven package repository. The gitea_username and gitea_token CI secrets must be set. For local consumption, running mvn install from the root is enough.

Features

  • Request and response models are immutable records with builders and immutable with... withers generated by record-builder — no hand-written builder boilerplate.
  • Full coverage of the documented Kodik filter parameters on SearchRequest and ListRequest, including the additional with_* response params.
  • Typed value sets for the stringly-typed parameters:
    • MediaType (media types, with MediaType.join(...) for the comma-joined types filter)
    • MpaaRating (g, pg, pg-13, r, rx)
    • AnimeKind (tv, tv13, tv24, tv48, movie, special, ova, ona, music)
    • Quality (NHD/SD/HD → 360p/480p/720p) for video preferences
  • Search validation that requires at least one identifier (title, titleOrig, shikimoriId, kinopoiskId, imdbId, id, mdlId, worldartAnimationId, worldartCinemaId, worldartLink, playerLink).
  • Transport-agnostic KodikHttpClient SPI returning a sealed Result<T> (Success/Failure).
  • Optional Retrofit2 transport in a separate module, free of Quarkus/CDI code.
  • Player-page parsing: fluent metadataQuery(String) / videoLinksQuery(String) on KodikAPI and videoLinks() / metadata() on every search/list result, with optional translation, quality and episode preferences. Direct file links are decoded and normalized automatically.

Configuration

A KodikAPI instance is created from a KodikHttpClient implementation and a token:

new KodikAPI(KodikHttpClient transport, String token)

You supply the transport; the library ships one for Retrofit2.

Type: SearchStep

Pick exactly one identifier, then chain any number of filters on the returned SearchRequestBuilder and finish with build():

SearchRequest request = api.search()
        .byTitle("One Piece")
        .withTypes(MediaType.join(MediaType.ANIME, MediaType.ANIME_SERIAL))
        .withYear(1999)
        .withRatingMpaa(MpaaRating.PG_13.apiValue())
        .withMaterialData(true)
        .build();
api.search(request);

All documented filters are available: titleOrig, strict, fullMatch, the external ids, types, year, camrip, lgbt, translationId, translationType, animeKind, animeStatus, mydramalistTags, ratingMpaa, minimalAge, kinopoiskRating, imdbRating, shikimoriRating, animeStudios, genres, animeGenres, duration, playerLink, hasField*, prioritizeTranslations, blockTranslations, season, episode, notBlockedIn, countries, actors, directors, producers, writers, composers, editors, designers, operators, licensedBy, plus the withSeasons, withEpisodes, withEpisodesData and withPageLinks extras. limit defaults to 100.

list()

Type: ListRequestBuilder

The list endpoint needs no identifier, only optional filters plus the list-specific sort and order:

ListRequest request = api.list()
        .withAnimeKind(AnimeKind.TV.apiValue())
        .withSort("year")
        .withOrder("desc")
        .build();
api.list(request);

Result<T>

Every call returns a sealed Result<T>:

switch (result) {
    case Result.Success<SearchResponse> success -> handle(success.value());
    case Result.Failure failure -> log(failure.error(), failure.statusCode());
}

The embedded player behind a media link can be parsed into media metadata and the direct video file links. Two entry points exist: results obtained from search() / list(), and arbitrary player/media links through KodikAPI.

Results from search() and list()

Responses returned by KodikAPI.search(...) / KodikAPI.list(...) bind a VideoLinkExtractService to every result, so each MediaResult exposes the queries directly:

Result<SearchResponse> found = api.search(api.search().byTitle("One Piece").build());
if (found instanceof Result.Success<SearchResponse> success) {
    for (MediaResult media : success.value().results()) {

        // metadata of the media's player page (no preferences)
        Result<MediaMetadata> metadata = media.metadata();

        // decoded direct file links, with optional preferences
        Result<VideoLinks> links = media.videoLinks()
                .preferTranslation(158)     // optional
                .preferQuality(Quality.HD)  // optional, defaults to Quality.HD
                .preferEpisode(1)           // optional, defaults to 1
                .query();
    }
}

A response deserialized without going through KodikAPI is not bound, and videoLinks() / metadata() return a Result.Failure explaining that the response must be obtained through KodikAPI.

KodikAPI exposes the same queries for an arbitrary media or player link:

Result<MediaMetadata> metadata = api.metadataQuery("https://.../player-link")
        .preferTranslation(158)
        .preferQuality(Quality.SD)
        .preferEpisode(4)
        .execute();

Result<VideoLinks> links = api.videoLinksQuery("https://.../player-link")
        .preferQuality(Quality.HD)
        .execute();

KodikAPI.metadata(link) is a shortcut that executes a metadata query without preferences.

Preferences

When no translation is preferred, the player link is used as-is. When translations are preferred, the page metadata is fetched and the player URL is rebuilt from the first preferred translation available on the page as baseUrl/{mediaType}/{mediaId}/{mediaHash}/{quality}. Quality defaults to Quality.HD (720p) and the episode to 1.

Result models

  • MediaMetadata — getTitle(), getTranslations() (available translations as KodikTranslation with id, mediaId, mediaHash, mediaType, ...), and getEpisodes().
  • VideoLinks — getLinks() returns the extracted links as Map<String, List<VideoLinks.Link>>; each Link has getSrc() (decoded, normalized direct URL) and getType().

Contributing

If you'd like to contribute, please fork the repository and use a feature branch. Pull requests are warmly welcome.

Any value documented by the Kodik API that is missing from the request models or enums is a good first contribution: add it, keep the builders generated (no manual builder code), and run mvn clean install.

Licensing

The code in this project is licensed under the GNU Lesser General Public License, version 3 (or, at your option, any later version). See the LICENSE file for the full license text.

The LGPL lets applications and libraries link against this project without requiring the whole combined work to be released under the same copyleft terms — good fit for a library. Modifications to the library itself must stay LGPL. The library 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 LGPL for more details.