Skip to main content

Anilist

@magistr/anilistv2026.10.25.10· MIT· 1d agoBEHAVIORS
01README

AniList GraphQL API — search and fetch anime/manga, media details, user lists, trending, and a Telegram activity notifier with completed/dropped detection

02Release Notes

2026.10.25.10

  • Repo-wide republish; no functional change.
03Behaviors1
@magistr/anilistv2026.10.25.10extensions/models/anilist.ts

Global Arguments

ArgumentTypeDescription
mediaType?enumDefault media type for queries
accessToken?stringAniList personal access token — required for update-progress mutations. Get at: https://anilist.co/settings/developer
clickhouseUrl?stringClickHouse HTTP base URL (e.g. http://host:8123) — required for the ingest-scores charting pipeline
clickhouseDatabase?stringClickHouse database holding anilist_metadata + user_scores
clickhouseUser?stringClickHouse HTTP user
clickhousePassword?stringClickHouse HTTP password (wire via vault)
fn lookup(title: string, userName?: string, limit?: number)
Answer 'have I seen X, and what did I score it' from the LOCAL ClickHouse mirror instead of the AniList API — works while AniList is down or rate-limiting. Matches a title substring case-insensitively across romaji/english/native. Only covers what ingest-scores has mirrored (COMPLETED + CURRENT entries that carry a score), so an unscored or never-ingested entry reads as not-found rather than not-watched.
ArgumentTypeDescription
titlestringTitle substring, matched case-insensitively against romaji/english/native
userName?stringRestrict scores to this user (case-sensitive — user_scores ORDER BY is). Omit to show every user who scored the match.
limit?numberMax distinct media to return, most popular first
fn search(query: string, type?: enum, perPage?: number, page?: number, fetchAll?: boolean)
Search for anime or manga by title. Set fetchAll to paginate through all results automatically.
ArgumentTypeDescription
querystringSearch term
type?enumOverride default media type
perPage?numberResults per page (ignored when fetchAll is true)
page?numberPage number (ignored when fetchAll is true)
fetchAll?booleanFetch all pages automatically (max 5 pages / 250 results)
fn get(id: number)
Get detailed info for a specific anime/manga by AniList ID
ArgumentTypeDescription
idnumberAniList media ID
fn userlist(userName: string, type?: enum, status?: enum)
Get a user's public anime/manga list (returns all entries; AniList returns full lists in one response)
ArgumentTypeDescription
userNamestringAniList username
type?enumOverride default media type
status?enumFilter by list status
fn trending(sort?: enum, type?: enum, perPage?: number, page?: number, fetchAll?: boolean)
Get trending or popular anime/manga. Set fetchAll to paginate through all results automatically.
ArgumentTypeDescription
sort?enumSort order
type?enumOverride default media type
perPage?numberResults per page (ignored when fetchAll is true)
page?numberPage number (ignored when fetchAll is true)
fetchAll?booleanFetch all pages automatically (max 5 pages / 250 results)
fn watching(userName: string)
Get CURRENT anime list enriched with nextAiringEpisode info — shows what's airing and when.
ArgumentTypeDescription
userNamestringAniList username
fn seasonal(season?: enum, seasonYear?: number, perPage?: number, page?: number)
Browse anime for a specific season (WINTER/SPRING/SUMMER/FALL) and year. Defaults to current season.
ArgumentTypeDescription
season?enumSeason (defaults to current based on current month)
seasonYear?numberYear (defaults to current year)
perPage?numberResults per page
page?numberPage number
fn update-progress(mediaId: number, progress: number, status?: enum, customLists?: array, repeat?: number)
Update episode progress (and optionally status) for a media entry on AniList. Requires accessToken in globalArguments.
ArgumentTypeDescription
mediaIdnumberAniList media ID
progressnumberEpisode number to set as progress
status?enumList status override (omit to keep existing status)
customLists?arrayCustom lists this entry should belong to, by name. ABSOLUTE, not additive: AniList replaces the entry's ENTIRE custom-list membership with what you pass, so read the current membership (userlist now selects `customLists`) and pass the full set you want kept. `[]` removes it from every custom list.
repeat?numberTimes REWATCHED. This is how AniList records a finished rewatch: the entry stays COMPLETED and this counter goes up. Setting status REPEATING instead means 'currently rewatching' and leaves this at 0. Absolute, not an increment — read the current value first and pass value+1.
fn set-score(mediaId?: number, title?: string, score: number, status?: enum)
Set your score/rating for an anime on AniList. Looks up by title if mediaId not provided. Requires accessToken.
ArgumentTypeDescription
mediaId?numberAniList media ID (takes priority over title)
title?stringAnime title to search for if mediaId not given
scorenumberScore to set. Use 0–10 for standard 10-point scale or 0–100 for 100-point scale.
status?enumOptionally change list status at the same time (e.g. COMPLETED after finishing)
fn recent-activity(usernames?: array, usernamesFile?: string, lookbackMinutes?: number, maxPages?: number, telegramModel?: string, telegramChatId?: string, format?: enum, includeStatusChanges?: boolean, dryRun?: boolean, floorReset?: boolean)
Fan-out: fetch new list activity (episodes watched, plus completed/dropped verdicts) for a set of users since the last run, enrich with each user's list score, optionally post to Telegram via a @magistr/telegram/send model instance. Verdicts are reported in a dedicated 'Status changes' digest section and stored on the feed resource. Dedupe cursor advances ONLY after confirmed delivery (at-least-once); dryRun never advances it.
ArgumentTypeDescription
usernames?arrayAniList usernames to track (fallback when usernamesFile is absent/unreadable)
usernamesFile?stringPath to a file with anilist.co profile URLs or usernames, one per line (preferred source)
lookbackMinutes?numberFirst-run window: how far back to look for users without a cursor entry
maxPages?numberMax activity pages (50/page) per run
telegramModel?stringSwamp model name for Telegram send (e.g. tg-bot). Empty string disables sending.
telegramChatId?stringOverride target chat (numeric id or @channelusername). Empty = the telegram model's defaultChatId.
format?enumMessage format: 'rich' = Bot API 10.2 block digest (bold profile-linked usernames + linked titles, via sendRichMessage); 'html' = the legacy plain HTML digest (via sendMessage)
includeStatusChanges?booleanDetect list verdicts: report 'dropped' titles (otherwise filtered out) and lift 'completed' into a dedicated 'Status changes' digest section. Set false to restore the progress-only digest that folds completions inline.
dryRun?booleanCompute and store the feed but never send or advance the cursor
floorReset?booleanRecovery: lower pinned window floors of never-delivered users to the lookback cutoff — use after an upstream outage silently returned empty results and floors advanced past real activity
fn ingest-scores(usernames?: array, usernamesFile?: string, perChunk?: number, maxChunks?: number, metadataBatchSize?: number)
Ingest fan-out (charting pipeline): for each username, paginate their COMPLETED+CURRENT scored list (sequential, maxChunks cap) and write score rows to ClickHouse user_scores, then refresh anilist_metadata for every media id seen (deduped across users). The user_name written is byte-identical to the usernames-file spelling — user_scores ORDER BY is case-sensitive. This is the ONLY writer of these tables. Requires clickhouseUrl in globalArguments.
ArgumentTypeDescription
usernames?arrayAniList usernames to ingest (fallback when usernamesFile is absent/unreadable). Casing is preserved verbatim into user_scores.user_name.
usernamesFile?stringPath to a file with anilist.co profile URLs or usernames, one per line (preferred source)
perChunk?numberMediaListCollection entries per chunk
maxChunks?numberMax list chunks per user per run (sequential pagination cap)
metadataBatchSize?numberMedia ids per metadata fetch/insert batch (AniList Page perPage cap is 50)
fn refresh-metadata(metadataBatchSize?: number)
Backfill AniList metadata for media ids present in user_scores but absent from anilist_metadata. Companion to ingest-scores that fetches ONLY the gap and no user lists, so it never refetches unchanged titles or re-hammers the score endpoints. Requires clickhouseUrl.
ArgumentTypeDescription
metadataBatchSize?numberMedia ids per metadata fetch/insert batch (AniList Page perPage cap is 50)

Resources

search(1h)— Anime/manga search results
media(1h)— Detailed media info
userlist(1h)— User anime/manga list
trending(1h)— Trending or popular media
watching(1h)— CURRENT list enriched with next airing episode info
seasonal(6h)— Anime airing in a specific season/year
watchProgress(1h)— Result of update-progress or set-score mutation
activityFeed(7d)— Recent list activity across tracked users, with detected completed/dropped verdicts, formatted Telegram messages and delivery outcome
activityCursor(infinite)— Per-user dedupe cursor for recent-activity: last delivered activity id + cached AniList userId
userlistScored(30d)— Per-user score-ingest summary (observability): rows written to user_scores, media ids seen, list-chunk pagination cap
ingestRun(90d)— Score-ingest run marker across all users (observability ONLY — not a provenance gate)
metadataRefresh(90d)— Marker for a metadata-only backfill: how many score media ids lacked metadata and how many rows were written
lookupResult(7d)— Local title lookup against the ClickHouse mirror: which media matched and who scored them
04Previous Versions19
2026.10.15.9MIT

2026.10.15.9

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.8MIT

2026.10.15.8

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.6MIT

2026.10.15.6

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.5MIT

2026.10.15.5

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.4MIT

2026.10.15.4

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.3MIT

2026.10.15.3

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.2MIT

2026.10.15.2

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.1MIT

2026.10.15.1

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.14.2MIT(was none)

2026.10.14.2

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.

Modified 1 behaviors

2026.09.26.1

2026.09.26.1

Added

  • seasonal returns synopses, tags and relations. SEASONAL_QUERY now requests description(asHtml: false), tags { name rank isMediaSpoiler } and relations { edges { relationType node { id type title } } } }, so a season can be ranked by story ideas rather than genres alone, and sequels can be matched to a user's list by media id instead of by title. Purely additive: new fields on each result, no stored resource reshaped; same single request.
2026.09.21.1

2026.09.21.1

Added

  • Preferred score scale. Scores now honour each user's own AniList scale (mediaListOptions.scoreFormat), instead of printing a bare, scale-blind number. The bare score AniList returns is already in the user's scale, so this is a labelling change:

    • POINT_100 -> score 85/100, POINT_10/POINT_10_DECIMAL -> score 8.5/10;
    • POINT_5 -> filled/empty stars (★★★★☆);
    • POINT_3 -> the AniList smiley (🙁/😐/🙂).

    New exported helpers renderScore / scoreSuffix and a ScoreFormat type drive it. An unknown or absent format falls back to the old bare score N, so the change is backward-compatible and every pre-existing test stays green.

  • recent-activity carries the scale end-to-end: scoreFormat rides along on the existing ACTIVITY_SCORES_QUERY score-enrichment fetch (user { mediaListOptions { scoreFormat } }) — no extra request — and is threaded through ActivityItem -> mergeActivities/mergeStatusChanges -> both renderers and the stored activityFeed resource.

  • userlist surfaces a top-level scoreFormat (via a User selection in USERLIST_QUERY) so per-entry score is interpretable.

  • set-score surfaces scoreFormat on watchProgress (via a user field on the mutation) so the stored score's scale is known.

model.version/manifest.yaml move 2026.09.19.2 -> 2026.09.21.1. Purely additive — every new field is optional and no stored resource is reshaped.

2026.09.19.2

2026.09.19.2

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.09.17.1

2026.09.17.1

Changed

  • Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.09.01.1

2026.09.01.1

Added

  • update-progress gained an optional customLists argument, and userlist now selects customLists so the current membership can be read first.

AniList treats customLists as an absolute set on write: it replaces the entry's entire custom-list membership with whatever is passed. Sending an omitted or partial list therefore silently drops the entry from every other custom list.

Two consequences are baked into the implementation rather than left to the caller to remember:

  • the argument is forwarded only when the caller explicitly supplies one, so an ordinary progress update cannot wipe membership as a side effect;
  • [] is the documented way to remove an entry from all custom lists.

model.version/manifest.yaml move 2026.08.30.1 -> 2026.09.01.1. Purely additive — no stored resource is reshaped.

2026.08.19.1
2026.08.17.1

2026.08.17.1

Reconciles this package with the main-repo copy that had forked away from it, and adds completed/dropped verdict detection to the activity notifier.

The fork. ~/dev_tmp/swamp/extensions/models/anilist.ts — not a swamp extension source of this package, just a parallel copy — is what actually runs and what is deployed to swamp serve. The two diverged from a common ancestor at 2026.07.27.1: this package gained the AL1-AL4 hardening, JSDoc and the upgrades chain, while the main repo independently gained the #AniList heading hashtag (2026.07.30.2) and the ClickHouse lookup method (2026.08.16.1). Neither side ever had the other's work. Resolved as a real three-way merge against the 2026.07.27.1 ancestor; the four conflicts were all "both sides added code next to the same comment". Consequence worth stating plainly: production had been running without the AL1-AL4 hostile-response guards this package shipped on 2026-08-02. This release is the first time they reach the deployed notifier.

Added

  • Completed/dropped verdict detection on recent-activity. dropped activity was filtered out entirely by isConsumptionActivity (commented as "list housekeeping … noise") and completed was folded into a watch range by mergeActivities, so neither was ever a visible event — a 7-day sample over the live tracked users surfaced three real drops that had been silently discarded. New isStatusChangeActivity / isReportableActivity (which widens the consumption set by exactly one status), partitionActivities (splits verdicts out of the progress rows) and mergeStatusChanges (dedupes per (user, mediaId, status); best known score wins, since score enrichment is best-effort and a duplicate may carry null).
  • Both renderers grew an optional trailing status-changes section: buildRichMessage adds a Status changes paragraph (bold profile-linked user, verb, linked title, score) and formatActivityMessages the HTML equivalent. The footer became N users · M titles · K status changes, with users counted across BOTH sections so a drop from someone with no progress rows is not uncounted, and the titles clause dropped when only verdicts landed.
  • statusChanges + statusChangeCount on the activityFeed resource — added to the resource SCHEMA as well as the write, since the schema strips unknown keys and would otherwise have made the verdicts unqueryable while the digest still displayed them.
  • includeStatusChanges method argument (default true) — set it false to restore the previous progress-only digest with no redeploy, since it is a workflow step input rather than anything baked into the extension.

Changed

  • Both renderers take the status list as an OPTIONAL second parameter, so every pre-existing call site and assertion is untouched.
  • buildRichMessage opens with a '#AniList activity' header and sanity: model exposes exactly the N documented methods updated — they had been asserting pre-fork behavior (no hashtag, 11 methods) that the main repo superseded long ago.

Modified 1 behaviors

2026.08.02.1

2026.08.02.1

Real fixes for the four latent bugs (AL1-AL4) filed against gql()'s request/retry path in the local anilist-latent-bugs issue-lifecycle model, plus the test-backfill work that originally characterized them (both land in this one release). anilist.ts is modified — the four fixes are localized to gql(), its rate-limit state, and the call sites that thread the new per-invocation client through; the pure-helper surface and every GraphQL query/mutation const are untouched.

Fixed

  • AL1 — a 200 response with {data:null} and no errors[] no longer null-derefs downstream with an uncaught TypeError: gql() now throws a typed Error ("200 response with null data and no errors") when both are absent. recent-activity's per-user activities fetch is now wrapped in a try/catch (mirroring the existing user-id-resolution step), so one user's hostile/malformed page is recorded in usersFailed and the fan-out continues for every other tracked user instead of aborting the whole run.
  • AL2 — a non-JSON 200 response body (e.g. a WAF/CDN error page) no longer crashes gql() with an uncaught SyntaxError: the body is now read once via response.text() and the JSON.parse is guarded, producing the same handled AniList API error: non-JSON 200 response body: <body> shape the non-ok path already used.
  • AL3 — 429-in-body detection no longer relies on an exact e.status === 429 numeric equality: a numeric-string status ("429"), or a missing status field paired with a rate-limit-shaped message ("rate limit" / "too many request"), are now both recognized and retried (sleep 60s) instead of falling through to the generic AniList GraphQL errors: throw.
  • AL4 — the module-level rateLimit object (shared by every request in the process) is gone. gql() is now built per-invocation via makeGql(authToken?), which closes over its own {remaining, resetAt} state; fetchAllPages/refreshMetadata take the caller's gql client as a parameter instead of reaching for a module-level one, and every method's execute() creates exactly one client for its own invocation. A low-remaining/future-reset response from one method call can no longer force an unrelated later call to pre-flight-sleep. Deleting the global means any un-migrated call site fails deno task check rather than silently reverting to shared state.

Adversarial suite: six pins flipped from characterizing the bug to asserting the fix (anilist_adversarial_test.ts) — the two data:null pins (search and recent-activity), the non-JSON-body pin, the two 429-in-body-shape pins (now FakeTime retry-success assertions matching the numeric-status sibling), and the module-rateLimit-coupling pin (now asserts independent per-invocation state, waited === 0). The contract-fixture, methods, coverage, and property-invariant-flow suites are unaffected by the fix (they exercise well-formed fixtures) — 144 tests before and after, same count, six assertions changed shape. Suite header comments across all five test files reworded: the four suites above still characterize unchanged behavior, and the adversarial suite now documents AL1-AL4 as fixed rather than pinned bugs.

manifest.yaml/anilist.ts version bumped to 2026.08.02.1; added an identity upgrades[] entry (toVersion: "2026.08.02.1", upgradeAttributes: (old) => old) since globalArguments is unchanged. JSDoc added to the ~23 exported symbols in anilist.ts (converted from // block comments), earning the symbols-docs quality factor now that anilist.ts is an active fix target rather than a frozen characterization surface — quality.yaml's ratchet moves from a measured 92% to a measured 100% (14/14, allPassed).

Out of scope: update-progress/set-score's mutation paths use raw fetch+resp.json() (not gql()), share AL2's non-JSON-body shape, but are not in the filed catalogue and are already null-safe via optional chaining — left unchanged.

Test backfill (wave-2c, ext-quality-bf-anilist, child of ext-quality-test-backfill)

  • Added extensions/models/anilist_test.ts (contract-fixture — pins the concrete AniList GraphQL wire shapes for all 11 methods from fixtures/*.json, plus the three ingest query-const invariants (LIST_INGEST_QUERY / USERLIST_QUERY / METADATA_INGEST_QUERY) migrated from the old file), anilist_methods_test.ts (methods — every one of the 11 methods' happy+error paths, args schema-parsed via model.methods.<m>.arguments.parse() before execute(); update-progress/

Notes trimmed at a line boundary to fit the registry's 4900-byte per-version cap. Full section: https://github.com/umag/swamp-workspace/blob/d850d8e8372d4c0008c9245959a090b37095de7a/anilist/CHANGELOG.md

2026.07.27.1

Merge pull request #61 from umag/feat/anilist-model-full

feat(anilist): bring @magistr/anilist up to the full model

Modified 1 behaviors

2026.07.16.2

Release 2026.07.16.2 — align model versions with manifests

Maintenance release across the @magistr extensions. For most packages this carries no functional change: the only edit is the model's version: field, brought back in line with its manifest version so the published model type version and the package version no longer drift.

Functional changes in this release are limited to:

  • anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.

  • arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.

Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.

Added 1, removed 1 behaviors

05Stats
A
100 / 100
Downloads
34
Archive size
53.4 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit2/2earned
  • Docs keep secrets in a vault1/1earned
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License2/2earned
  • Verified public repository2/2earned
06Platforms
07Labels