Skip to main content

Anime Cron

@magistr/anime-cronv2026.10.25.10· MIT· 1d agoBEHAVIORS
01README

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 into archiveContainerDir, 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>
02Release Notes

2026.10.25.10

  • Repo-wide republish; no functional change.
03Behaviors1
@magistr/anime-cronv2026.10.25.10extensions/models/anime_cron.ts

Global Arguments

ArgumentTypeDescription
anilistUserstringAniList username
maxCacheAgeDays?numberHow stale the cached watch list may be before fetch-airing fails instead of falling back to it
anilistToken?stringAniList personal access token — required for update-progress. Get at: https://anilist.co/settings/developer
transmissionRpcUrlstringTransmission RPC URL (e.g. http://host:9091/transmission/rpc)
transmissionUserstringTransmission RPC username
transmissionPassstringTransmission RPC password
archiveContainerDir?stringDownload 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?stringDownload dir prefix INSIDE the Transmission container for anime (e.g. /anime/tv)
preferredResolution?numberPreferred resolution: 720/1080/2160
telegramModel?stringSwamp model name for Telegram send (@magistr/telegram-send instance). Set to empty string to disable notifications.
fn seed-watchlist(force?: boolean)
Reconstruct the watch-list cache from the most recent fetch-airing result, so downloads can continue when AniList went down before any run had cached the real list. Airing times are INFERRED; the next successful AniList read replaces this.
ArgumentTypeDescription
force?booleanOverwrite an existing cache. Refused by default so a real AniList capture is never replaced by an inferred one.
fn fetch-airing(dryRun?: boolean, skipUnaired?: boolean)
Check AniList CURRENT list → search Nyaa for next episode of each airing show → add to Transmission. Skips episodes not yet aired.
ArgumentTypeDescription
dryRun?booleanIf true, find torrents but do not add to Transmission
skipUnaired?booleanSkip shows where next episode has not aired yet (default: true)
fn notify-downloaded(completions: array, dryRun?: boolean)
Announce finished anime episodes on Telegram, each with inline buttons: Watched, or a 1-10 score. Fed by the Transmission completion event (unraid-transmission poll-completed newCompletions, in the transmission-complete-notify workflow); non-anime torrents are ignored. A tap goes through the telegram-callback workflow to rate-episode.
ArgumentTypeDescription
completionsarrayNewly finished torrents — poll-completed's newCompletions
dryRun?booleanMatch finished torrents to shows but send nothing
fn rate-episode(callback: string, by?: string)
Apply a Telegram rating tap to AniList: progress = max(current, episode) and, for a score 1-10, the show's score. Completes the entry at the last episode. Idempotent — a repeated tap sets the same values.
ArgumentTypeDescription
callbackstringThe tap's arg: <mediaId>:<episode>:<score>, score 0 = watched only
by?stringWho tapped
fn mark-watched(sinceHours?: number, dryRun?: boolean)
Check recently completed anime torrents in Transmission → update AniList progress. Requires anilistToken in globalArguments.
ArgumentTypeDescription
sinceHours?numberWindow for 'recently completed' torrents in hours (default: 25 to safely cover daily runs)
dryRun?booleanFind completed torrents and AniList matches but do not mutate AniList
fn upgrade-bd(minScore?: number, dryRun?: boolean, libraryEntries?: array)
Check AniList COMPLETED list against SeaDex → queue BD release downloads for shows that have a 'best' release but are not yet upgraded. Safe to run repeatedly — Transmission deduplicates.
ArgumentTypeDescription
minScore?numberMinimum AniList average score to consider for BD upgrade (0 = all)
dryRun?booleanFind BD releases but do not add to Transmission
libraryEntries?arrayPer-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.
fn fetch-archive(groups?: array, category?: string, maxPages?: number, minSeeders?: number, seedForeverExisting?: boolean, dryRun?: boolean)
Sweep Nyaa for every release credited to a set of preservation groups (default Kineko Video + LonelyChaser), queue them into the archive dir, and switch them to unlimited ratio/idle seeding so they seed forever. Idempotent: releases already in Transmission are reported as duplicates, and the seed-forever setting is re-applied to everything already in the archive dir.
ArgumentTypeDescription
groups?arrayRelease-group names matched against the leading [..] credit in each torrent title. Suffixed aliases (LonelyChaser-Raws) and collabs ([LonelyChaser & Kineko Video]) both match.
category?stringNyaa category filter. Default 0_0 (all) — these groups also post live-action scans outside the anime categories.
maxPages?numberMax Nyaa RSS pages per group (75 releases per page)
minSeeders?numberSkip releases with fewer seeders than this (0 = take dead torrents too)
seedForeverExisting?booleanAlso apply unlimited seeding to torrents already in the archive dir
dryRun?booleanResolve what would be queued but do not touch Transmission
fn disk-stats()
Query Transmission for all anime torrents and return total/downloaded/remaining disk usage grouped by status.

Resources

fetchResult(7d)— Outcome of fetch-airing: what was found and queued
watchlist(infinite)— Last CURRENT list successfully read from AniList, used to keep downloading while AniList is unreachable
notFoundAlerts(infinite)— Episodes fetch-airing has already sent a not-found alert for, so each is announced once
markResult(7d)— Outcome of mark-watched: which episodes were synced to AniList
downloadNotice(7d)— Outcome of notify-downloaded: which finished episodes got a Telegram notice with rating buttons
rateResult(30d)— Outcome of rate-episode: the AniList progress/score a Telegram tap set
upgradeResult(7d)— Outcome of upgrade-bd: which BD releases were queued
archiveResult(7d)— Outcome of fetch-archive: which preservation-group releases were queued and set to seed forever
diskStats(7d)— Disk usage summary for all anime torrents in Transmission
04Previous Versions19
2026.10.25.1MIT

2026.10.25.1

Changed

  • README: swamp vault put now prompts for secrets (hidden input) instead of taking them on the command line, where shell history keeps them.
2026.10.20.1MIT

2026.10.20.1

  • fetch-airing falls 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 (AniList startDate), 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.
  • parseEpisode returns null for batches (new parseBatchRange), 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 notFoundAlerts resource records what was already announced, so the hourly run stops repeating them. fetchResult gains alertsDeduped.

Modified 1 behaviors

2026.10.18.1MIT

2026.10.18.1

  • fetch-airing keeps 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.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.
2026.10.14.1

2026.10.14.1

  • Rating buttons when an episode finishes downloading. New notify-downloaded takes the finished torrents from the Transmission completion event (@transmission/api poll-completed newCompletions), keeps those under animeContainerDir, 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

2026.10.02.1

  • fetch-airing no 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] … S02E01 was queued because pickBest compared only the episode number. Hits now carry their RSS pubDate; with a real AniList airing time, pickBest rejects 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-watched and 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

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.3

2026.09.01.3

  • Republish of 2026.09.01.2, no code change. The 2026.09.01.2 publish was skipped when fmt:check failed on the merge commit (a deno 2.8/2.9 markdown reflow difference in this file), and extension-publish only fires on the commit that carries the version bump, so the registry never received it.
2026.09.01.1

2026.09.01.1

  • parseEpisode now reads the SxxExx form. The bare E-prefix pattern documented itself as covering "S01E05", but \b before the E never fires when a digit precedes it, so every S01E09-style release parsed as null and pickBest had 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) used S01E09, so the show sat in not-found for five hourly runs. A dedicated \bS\d{1,2}E(\d{1,3})\b check now runs right after the - NN fansub convention.
2026.08.22.2

2026.08.22.2

  • An inferred airing time no longer drives an alert. The not-found branch derives airedAtSec from nextAiringAt, but on a cache-backed run that timestamp is projected — and on a SEEDED list it is simply the capture instant. With epsBehind = 0 it 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 alertsSuppressed count on fetchResult, so a withheld page is visible rather than silent. It is incremented independently of whether telegramModel is configured, which is also what makes the behaviour testable: sendTg spawns swamp via Deno.Command, so a fake context.runModel observes nothing and such a test passes vacuously. The new test pairs the cache case with an AniList-path positive control.

  • listSource and listAgeSeconds are now declared in FetchResultSchema. They shipped in 2026.08.22.1 and worked — undeclared keys pass through — but carried no schema or description.

05Stats
A
100 / 100
Downloads
49
Archive size
47.3 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