Anime Cron
Anime automation pipeline: fetch airing episodes from Nyaa, sync watch progress back to AniList, queue Blu-ray upgrades via SeaDex, and archive preservation-group releases with unlimited seeding.
Methods:
fetch-airing— check AniList CURRENT list → search Nyaa for the next episode of each show → add to Transmission. Skips shows not yet aired. Supports multi-episode catchup and name-based dedup. Optional Telegram notifications when an episode is queued or overdue.mark-watched— scan recently completed Transmission torrents → update AniList progress. Matches by show title via AniList search.upgrade-bd— check AniList COMPLETED list against SeaDex → queue BD release downloads for shows that have a recommended release not yet on disk. Idempotent (Transmission deduplicates).fetch-archive— sweep Nyaa for every release credited to a set of preservation groups (default Kineko Video + LonelyChaser), queue them intoarchiveContainerDir, and switch them to unlimited ratio/idle seeding so an archive never stops seeding. Matches on the leading[..]credit, so collabs and suffixed aliases are caught but a title that only mentions the group is not. Idempotent via infoHash dedup against live Transmission state.notify-downloaded— fed the Transmission completion event (newCompletions), post " epN downloaded" on Telegram with inline buttons: Watched, or a 1-10 score. Non-anime torrents are ignored.</li> <li><code>rate-episode</code> — apply a button tap (<code>anr:<mediaId>:<ep>:<score></code>) to AniList: progress = max(current, episode), score 1-10 sets the show score, the last episode completes the entry. Idempotent.</li> <li><code>disk-stats</code> — query Transmission for all anime torrents and return total/downloaded/remaining disk usage grouped by status.</li> </ul> <p>Requires a running Transmission RPC endpoint and an AniList account. <code>mark-watched</code> and <code>fetch-airing</code> Telegram alerts require <code>anilistToken</code>. Set <code>telegramModel</code> to a <code>@magistr/telegram-send</code> model instance name to enable notifications.</p>
2026.10.25.10
- Repo-wide republish; no functional change.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| anilistUser | string | AniList username |
| maxCacheAgeDays? | number | How stale the cached watch list may be before fetch-airing fails instead of falling back to it |
| anilistToken? | string | AniList personal access token — required for update-progress. Get at: https://anilist.co/settings/developer |
| transmissionRpcUrl | string | Transmission RPC URL (e.g. http://host:9091/transmission/rpc) |
| transmissionUser | string | Transmission RPC username |
| transmissionPass | string | Transmission RPC password |
| archiveContainerDir? | string | Download dir for fetch-archive preservation rips, as the Transmission CONTAINER sees it (e.g. /anime/kineko) — not the host path it is bind-mounted from |
| animeContainerDir? | string | Download dir prefix INSIDE the Transmission container for anime (e.g. /anime/tv) |
| preferredResolution? | number | Preferred resolution: 720/1080/2160 |
| telegramModel? | string | Swamp model name for Telegram send (@magistr/telegram-send instance). Set to empty string to disable notifications. |
| Argument | Type | Description |
|---|---|---|
| force? | boolean | Overwrite an existing cache. Refused by default so a real AniList capture is never replaced by an inferred one. |
| Argument | Type | Description |
|---|---|---|
| dryRun? | boolean | If true, find torrents but do not add to Transmission |
| skipUnaired? | boolean | Skip shows where next episode has not aired yet (default: true) |
| Argument | Type | Description |
|---|---|---|
| completions | array | Newly finished torrents — poll-completed's newCompletions |
| dryRun? | boolean | Match finished torrents to shows but send nothing |
| Argument | Type | Description |
|---|---|---|
| callback | string | The tap's arg: <mediaId>:<episode>:<score>, score 0 = watched only |
| by? | string | Who tapped |
| Argument | Type | Description |
|---|---|---|
| sinceHours? | number | Window for 'recently completed' torrents in hours (default: 25 to safely cover daily runs) |
| dryRun? | boolean | Find completed torrents and AniList matches but do not mutate AniList |
| Argument | Type | Description |
|---|---|---|
| minScore? | number | Minimum AniList average score to consider for BD upgrade (0 = all) |
| dryRun? | boolean | Find BD releases but do not add to Transmission |
| libraryEntries? | array | Per-show library entries from anime-library.scan — used to skip shows already on disk as the SeaDex best release. Pass data.latest('anime-library','current').attributes.entries. |
| Argument | Type | Description |
|---|---|---|
| groups? | array | Release-group names matched against the leading [..] credit in each torrent title. Suffixed aliases (LonelyChaser-Raws) and collabs ([LonelyChaser & Kineko Video]) both match. |
| category? | string | Nyaa category filter. Default 0_0 (all) — these groups also post live-action scans outside the anime categories. |
| maxPages? | number | Max Nyaa RSS pages per group (75 releases per page) |
| minSeeders? | number | Skip releases with fewer seeders than this (0 = take dead torrents too) |
| seedForeverExisting? | boolean | Also apply unlimited seeding to torrents already in the archive dir |
| dryRun? | boolean | Resolve what would be queued but do not touch Transmission |
Resources
2026.10.25.1
Changed
- README:
swamp vault putnow prompts for secrets (hidden input) instead of taking them on the command line, where shell history keeps them.
2026.10.20.1
fetch-airingfalls back to batch releases. Netflix released all of "Cyberpunk: Edgerunners 2" at once, and Nyaa carried it only as batches ("- 01 ~ 10", "S02 … Batch"), so on 2026-10-20 eps 3–10 never matched a single-episode pick. When episodes have no single-episode release, one batch covering all of them is added, and its files for episodes already on disk are deselected. A batch must be uploaded no more than 14 days before the season premiered (AniListstartDate), which keeps season-1 packs out; with no premiere date no batch is taken. A pack with no stated range only counts once the season is finished.parseEpisodereturns null for batches (newparseBatchRange), so "- 01 ~ 10" no longer reads as episode 1. Transmission dedup counts a batch for every episode in its range.- Not-found alerts go out once. One Telegram message per show lists every
missing episode ("ep3–10 not found"), and a new
notFoundAlertsresource records what was already announced, so the hourly run stops repeating them.fetchResultgainsalertsDeduped.
Modified 1 behaviors
2026.10.18.1
fetch-airingkeeps searching until it finds the target resolution. The search stopped as soon as ANY release of the episode turned up, so on 2026-10-18 a 720p English dub found by the English title ended the search for "Mezametara Saikyou Soubi to Uchuusen-mochi datta node, …" ep 3, and the synonym search that finds SubsPlease ("Saikyosoubi") never ran.- Shorter fallback queries (
shortTitleQueries). Nyaa matches every query word literally, and releases drop the English title's punctuation and shorten long romaji titles. After the full titles, synonyms and base title, the search now tries the English title without punctuation, the English title before ":", the romaji before ",", the romaji without punctuation and its first three words.
2026.10.15.9
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.8
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.6
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.5
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.4
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.3
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.2
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.15.1
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.14.2
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.10.14.1
- Rating buttons when an episode finishes downloading. New
notify-downloadedtakes the finished torrents from the Transmission completion event (@transmission/apipoll-completednewCompletions), keeps those underanimeContainerDir, maps each download folder back to its AniList CURRENT entry and sends " epN downloaded" with inline buttons Watched and 1-10 (<code>callback_data</code> <code>anr:<mediaId>:<episode>:<score></code>, 0 = watched only). A send Telegram rejects fails the method rather than vanishing.</li> <li><strong><code>rate-episode</code></strong> applies a tap, routed by a <code>telegram-callback</code> workflow on <code>kind == "anr"</code>: progress becomes max(current, episode) so an older button never rewinds it, a score sets the show score (AniList has no per-episode score, <code>scoreRaw</code> = score x 10), and reaching the last episode completes the entry. Re-tapping sets the same values.</li> </ul>
Modified 1 behaviors
2026.10.02.1
fetch-airingno longer takes an older season's release for a sequel episode. On 2026-10-02 "Kusuriya no Hitorigoto 3rd Season" ep 1 aired, no season-3 release existed yet, the base-title fallback searched plain "Kusuriya no Hitorigoto", and a January 2025[Half-Baked] … S02E01was queued becausepickBestcompared only the episode number. Hits now carry their RSSpubDate; with a real AniList airing time,pickBestrejects releases uploaded more than 14 days before the episode aired (pre-airs still pass) and releases labelled with a different season (parseSeason).- Continuous numbering. SubsPlease numbers some sequels on from the previous
season (season 3 ep 1 = "- 49"). For the latest aired episode only, when
nothing carries the season number, a higher-numbered release uploaded in that
week's window is accepted if every such release agrees on one number. It is
renamed in Transmission (
torrent-rename-path) to the season's own folder name and episode number, so seanime,mark-watchedand dedup read the right episode. - Dedup is keyed by download folder + episode (
episodeKey), not by a season-stripped show name, so season 1's "- 01" can no longer block a sequel's ep 1.
Modified 1 behaviors
2026.09.19.2
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.09.17.1
Changed
- Repo-wide maintenance release: version bump to republish the current source. No schema change.
2026.09.01.3
- Republish of 2026.09.01.2, no code change. The 2026.09.01.2 publish was
skipped when
fmt:checkfailed on the merge commit (a deno 2.8/2.9 markdown reflow difference in this file), andextension-publishonly fires on the commit that carries the version bump, so the registry never received it.
2026.09.01.1
parseEpisodenow reads theSxxExxform. The bare E-prefix pattern documented itself as covering "S01E05", but\bbefore theEnever fires when a digit precedes it, so everyS01E09-style release parsed asnullandpickBesthad nothing to rank. Seen live on 2026-09-01: Koukaku Kidoutai (2026) ep 9 — Erai-raws (the group behind eps 1–8, dash form) never posted, and all nine other 1080p releases (VARYG, Judas, DKB, ToonsHub, Ironclad, Cattleya) usedS01E09, so the show sat innot-foundfor five hourly runs. A dedicated\bS\d{1,2}E(\d{1,3})\bcheck now runs right after the- NNfansub convention.
2026.08.22.2
An inferred airing time no longer drives an alert. The not-found branch derives
airedAtSecfromnextAiringAt, but on a cache-backed run that timestamp is projected — and on a SEEDED list it is simply the capture instant. WithepsBehind = 0it therefore measured the age of the cache rather than of the episode, so past the 30-minute grace every show read as overdue on every run: 13 shows produced an identical "Aired 298min ago — not yet on Nyaa" every hour.The overdue Telegram alert is now gated on
listSource === "anilist". The not-found outcome is still recorded, so the run report shows exactly what was missing; only the page is withheld until AniList answers and the times are real again. The "queued" alert is deliberately NOT gated — a started download is a fact regardless of where the list came from.New
alertsSuppressedcount onfetchResult, so a withheld page is visible rather than silent. It is incremented independently of whethertelegramModelis configured, which is also what makes the behaviour testable:sendTgspawnsswampviaDeno.Command, so a fakecontext.runModelobserves nothing and such a test passes vacuously. The new test pairs the cache case with an AniList-path positive control.listSourceandlistAgeSecondsare now declared inFetchResultSchema. They shipped in 2026.08.22.1 and worked — undeclared keys pass through — but carried no schema or description.
- 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