Anilist
AniList GraphQL API — search and fetch anime/manga, media details, user lists, trending, and a Telegram activity notifier with completed/dropped detection
2026.09.01.1
Added
update-progressgained an optionalcustomListsargument, anduserlistnow selectscustomListsso 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.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| mediaType | enum | Default media type for queries |
| accessToken? | string | AniList personal access token — required for update-progress mutations. Get at: https://anilist.co/settings/developer |
| clickhouseUrl? | string | ClickHouse HTTP base URL (e.g. http://host:8123) — required for the ingest-scores charting pipeline |
| clickhouseDatabase | string | ClickHouse database holding anilist_metadata + user_scores |
| clickhouseUser | string | ClickHouse HTTP user |
| clickhousePassword? | string | ClickHouse HTTP password (wire via vault) |
| Argument | Type | Description |
|---|---|---|
| title | string | Title substring, matched case-insensitively against romaji/english/native |
| userName? | string | Restrict scores to this user (case-sensitive — user_scores ORDER BY is). Omit to show every user who scored the match. |
| limit | number | Max distinct media to return, most popular first |
| Argument | Type | Description |
|---|---|---|
| query | string | Search term |
| type? | enum | Override default media type |
| perPage | number | Results per page (ignored when fetchAll is true) |
| page | number | Page number (ignored when fetchAll is true) |
| fetchAll | boolean | Fetch all pages automatically (max 5 pages / 250 results) |
| Argument | Type | Description |
|---|---|---|
| id | number | AniList media ID |
| Argument | Type | Description |
|---|---|---|
| userName | string | AniList username |
| type? | enum | Override default media type |
| status? | enum | Filter by list status |
| Argument | Type | Description |
|---|---|---|
| sort | enum | Sort order |
| type? | enum | Override default media type |
| perPage | number | Results per page (ignored when fetchAll is true) |
| page | number | Page number (ignored when fetchAll is true) |
| fetchAll | boolean | Fetch all pages automatically (max 5 pages / 250 results) |
| Argument | Type | Description |
|---|---|---|
| userName | string | AniList username |
| Argument | Type | Description |
|---|---|---|
| season? | enum | Season (defaults to current based on current month) |
| seasonYear? | number | Year (defaults to current year) |
| perPage | number | Results per page |
| page | number | Page number |
Resources
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.droppedactivity was filtered out entirely byisConsumptionActivity(commented as "list housekeeping … noise") andcompletedwas folded into a watch range bymergeActivities, 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. NewisStatusChangeActivity/isReportableActivity(which widens the consumption set by exactly one status),partitionActivities(splits verdicts out of the progress rows) andmergeStatusChanges(dedupes per(user, mediaId, status); best known score wins, since score enrichment is best-effort and a duplicate may carrynull). - Both renderers grew an optional trailing status-changes section:
buildRichMessageadds aStatus changesparagraph (bold profile-linked user, verb, linked title, score) andformatActivityMessagesthe HTML equivalent. The footer becameN 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+statusChangeCounton theactivityFeedresource — 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.includeStatusChangesmethod argument (defaulttrue) — set itfalseto 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' headerandsanity: model exposes exactly the N documented methodsupdated — they had been asserting pre-fork behavior (no hashtag, 11 methods) that the main repo superseded long ago.
Modified 1 models
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 noerrors[]no longer null-derefs downstream with an uncaughtTypeError:gql()now throws a typedError("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 inusersFailedand 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 uncaughtSyntaxError: the body is now read once viaresponse.text()and theJSON.parseis guarded, producing the same handledAniList 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 === 429numeric equality: a numeric-string status ("429"), or a missingstatusfield paired with a rate-limit-shapedmessage("rate limit" / "too many request"), are now both recognized and retried (sleep 60s) instead of falling through to the genericAniList GraphQL errors:throw. - AL4 — the module-level
rateLimitobject (shared by every request in the process) is gone.gql()is now built per-invocation viamakeGql(authToken?), which closes over its own{remaining, resetAt}state;fetchAllPages/refreshMetadatatake the caller'sgqlclient as a parameter instead of reaching for a module-level one, and every method'sexecute()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 failsdeno task checkrather 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 fromfixtures/*.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 viamodel.methods.<m>.arguments.parse()beforeexecute();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
Merge pull request #61 from umag/feat/anilist-model-full
feat(anilist): bring @magistr/anilist up to the full model
Modified 1 models
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 models
Merge pull request #5 from umag/extensions/jscad-stl-pair
extensions: add 15 more @magistr extensions + auto-discover CI
- 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
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned