# `Laev.MAL`
[🔗](https://github.com/alexdont/laev/blob/main/lib/laev/mal.ex#L1)

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.

# `anime`

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`

The MAL authorize URL to open in the browser.

# `client_id`

# `client_secret`

# `configured?`

# `episodes_watched`

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

# `exchange_code`

Exchange an auth code for tokens and persist them.

# `list`

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`

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

# `login`

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`

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`

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

# `redirect_uri`

# `set_progress`

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`

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
