Laev.MAL (laev v0.1.40)

Copy Markdown View Source

MyAnimeList API v2 client — anime list scrobbling.

OAuth is authorization-code + PKCE (MAL only supports the "plain" challenge, so verifier == challenge). The owner ships a MAL_CLIENT_ID (and secret if their app has one); each user authorizes their own account once via laev mal login, and the tokens live in ~/.laev/mal.json, auto-refreshed.

Everything returns values, never raises.

Summary

Functions

One anime, as laev needs to describe it: %{title:, episodes:, seconds:, status:, score:} — or nil. Public data, so it works logged out too.

True once the user has logged in (a token is stored).

The MAL authorize URL to open in the browser.

Episodes the user has watched of this anime on MAL (0 if unknown).

Exchange an auth code for tokens and persist them.

Every anime on the user's list, newest fields and all: [%{mal_id:, title:, status:, episodes_watched:, episodes:, episode_seconds:, kind:}].

The user's list status for an anime: %{status, episodes_watched, score} or nil (not on list / not logged in).

Run the full login: open the browser to MAL, wait for the redirect on the local listener, exchange the code. Returns {:ok, username} | {:error, reason}. open_browser is a 1-arg fn given the authorize URL.

Forget the stored tokens (logout).

A PKCE verifier (also used verbatim as the plain challenge).

What to send for "watched episode N of M": the count, the status, and a finish date on the episode that completes it.

Set the user's score (1–10) for an anime.

Record progress: set watched count and status. Never regresses a higher count already on MAL. total (episode count, optional) decides whether finishing marks the show completed.

Set the list status of an anime — "watching", "on_hold", "completed", "dropped", "plan_to_watch" — without touching the episode count.

The logged-in user's MAL username, or nil.

Functions

anime(mal_id)

One anime, as laev needs to describe it: %{title:, episodes:, seconds:, status:, score:} — or nil. Public data, so it works logged out too.

authenticated?()

True once the user has logged in (a token is stored).

authorize_url(state, verifier)

The MAL authorize URL to open in the browser.

client_id()

client_secret()

configured?()

episodes_watched(mal_id)

Episodes the user has watched of this anime on MAL (0 if unknown).

exchange_code(code, verifier)

Exchange an auth code for tokens and persist them.

list(report \\ fn _line -> :ok end)

Every anime on the user's list, newest fields and all: [%{mal_id:, title:, status:, episodes_watched:, episodes:, episode_seconds:, kind:}].

Paged a thousand at a time — a long-standing list runs to several hundred entries, and MAL will hand them all over, just not at once. nsfw: true because a list laev imports has to be the list the user actually has.

list_status(mal_id)

The user's list status for an anime: %{status, episodes_watched, score} or nil (not on list / not logged in).

login(open_browser)

Run the full login: open the browser to MAL, wait for the redirect on the local listener, exchange the code. Returns {:ok, username} | {:error, reason}. open_browser is a 1-arg fn given the authorize URL.

logout()

Forget the stored tokens (logout).

new_verifier()

A PKCE verifier (also used verbatim as the plain challenge).

progress_fields(count, total)

What to send for "watched episode N of M": the count, the status, and a finish date on the episode that completes it.

The last episode is the whole point — a list that still says watching after the finale is a list you have to go and tidy by hand — so reaching the episode count flips the entry to completed and dates it today. An anime whose total MAL doesn't know (a running series, a long-form shounen) stays watching, because nothing here can tell when it ends.

rate(mal_id, score)

Set the user's score (1–10) for an anime.

redirect_uri()

set_progress(mal_id, episode, total \\ nil)

Record progress: set watched count and status. Never regresses a higher count already on MAL. total (episode count, optional) decides whether finishing marks the show completed.

Adds the anime to the list if it isn't on it — MAL's PATCH is an upsert, so starting something you never planned to watch just appears as watching.

Returns {:ok, fields} | {:error, reason} | :skip.

set_status(mal_id, status)

Set the list status of an anime — "watching", "on_hold", "completed", "dropped", "plan_to_watch" — without touching the episode count.

username()

The logged-in user's MAL username, or nil.