Skip to main content

Anime Cron

@magistr/anime-cronv2026.09.01.3· 9d agoMODELS
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.
  • disk-stats — query Transmission for all anime torrents and return total/downloaded/remaining disk usage grouped by status.

Requires a running Transmission RPC endpoint and an AniList account. mark-watched and fetch-airing Telegram alerts require anilistToken. Set telegramModel to a @magistr/telegram-send model instance name to enable notifications.

02Release Notes

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.
03Models1
@magistr/anime-cronv2026.09.01.3extensions/models/anime_cron.ts

Global Arguments

ArgumentTypeDescription
anilistUserstringAniList username
maxCacheAgeDaysnumberHow 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
archiveContainerDirstringDownload 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
animeContainerDirstringDownload dir prefix INSIDE the Transmission container for anime (e.g. /anime/tv)
preferredResolutionnumberPreferred resolution: 720/1080/2160
telegramModelstringSwamp model name for Telegram send (@magistr/telegram-send instance). Set to empty string to disable notifications.

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
markResult(7d)— Outcome of mark-watched: which episodes were synced to AniList
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 Versions7
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.

2026.08.22.1

2026.08.22.1

  • Merged the two divergent copies of this model into one. The main repo's extensions/models/anime_cron.ts was the LIVE file and this package was inert; each had work the other lacked. Reconciled with the repo behaviour kept where they conflicted: pickBest restores the HARD resolution floor (h.resolution >= targetRes) rather than the soft ranking tried here, and the "varyg": 7 preferred-group score is back. The property test's oracle now applies the same floor — mirroring only the scoring made it nominate winners pickBest had already discarded.

  • New seed-watchlist method. Rebuilds the watch-list cache from the most recent fetchResult for the case the cache cannot cover: AniList went down BEFORE any run had cached the real list. Progress per show comes from the strongest signal available (duplicate means that episode is already in Transmission, anything else means it is still owed), taking the highest across outcomes. Airing times are INFERRED from the capture, so the payload is flagged seeded: true and the next successful AniList read replaces it. Refuses to overwrite an existing cache without force.

  • Resource instances renamed to watchlist-current. writeResource takes (spec, INSTANCE) but readResource takes the INSTANCE — so the cache both read the wrong key and collided with fetchResult, which already owns the instance name current.

  • fetch-airing keeps working while AniList is down. Every successful run now caches the CURRENT list to a watchlist resource, and a failed AniList read falls back to it instead of aborting the run. On 2026-08-22 three consecutive hourly runs failed on AniList 403 and nothing downloaded for three hours; nothing about downloading needs AniList reachable, only knowing what is being watched.

  • The cached list is projected forward, not replayed. lastAiredEp is derived as nextAiringEp - 1, so a raw snapshot would conclude nothing new had aired and download nothing — the very failure it exists to fix. TV anime airs weekly, so episodes aired since the capture are the whole weeks elapsed since nextAiringAt, plus the one airing at that instant. Capped at the season total so a finished show is never projected past its last episode.

  • New maxCacheAgeDays global argument (default 14). Past that the run fails as before: shows end and new ones start, so downloading against a month-old list is worse than failing loudly. A cache with an unparseable timestamp counts as infinitely old.

  • fetchResult gains listSource and listAgeSeconds, so a run served from a stale list is visible rather than looking identical to a healthy one.

  • No behaviour change when AniList is reachable, and AniList-down with no cache still rejects exactly as before.

Modified 1 models

2026.08.19.1
2026.08.08.1

2026.08.08.1

Added fetch-archive: a preservation-archive sweep that pulls every Nyaa release credited to a set of release groups and pins it to seed forever.

Motivation: groups like Kineko Video and LonelyChaser publish fresh 16mm/35mm film scans, LaserDisc Domesday captures and VHS restorations of material that never got a digital release. Keeping a complete, permanently-seeding copy is the point — but the existing fetch-airing/upgrade-bd methods are both driven off an AniList list, and none of this catalogue is on one.

  • New method fetch-archive. Pages Nyaa's RSS (75/page, maxPages default 10) once per group, keeps only releases whose leading [..] credit actually names a wanted group, dedups by infoHash across groups, queues the rest flat into the archive dir, and issues one batched torrent-set with seedRatioMode: 2 + seedIdleMode: 2 (Transmission's "unlimited", which overrides the session defaults per torrent).
    • Group matching splits collabs on & + , / and prefix-matches in both directions behind a 5-character stem floor, so a wanted LonelyChaser catches the LonelyChaser-Raws alias and a wanted Kineko Video catches a bare Kineko, while a short token cannot wildcard onto every group. A title that merely mentions a group without crediting it in the bracket is not swept.
    • Idempotent: it reads live Transmission state first and reports already-held releases as duplicates. seedForeverExisting (default true) re-applies unlimited seeding to everything already in the archive dir, so a re-run repairs torrents added before this method existed.
    • category defaults to 0_0 (all) because these groups also post live-action scans outside the anime categories.
    • Reports queuedGB/catalogGB so a dryRun answers "how much disk will this cost" before anything is queued.
    • Optional Telegram summary lists what was snatched (capped at 20 titles + an overflow count, to stay under Telegram's 4096-char limit).
  • New global argument archiveContainerDir (default /anime/kineko) — the download dir inside the Transmission container for these rips.
  • NyaaHit gains sizeBytes, parsed from <nyaa:size> by the new parseNyaaSize helper (returns 0 for anything unparseable, so a missing size can never NaN a running total).
  • New exported pure helpers, all unit-tested: parseNyaaSize, decodeEntities, escapeHtml, bracketGroups, normGroup, creditsGroup.

Tests: 117 pass. Added fetch-archive cases across the contract-fixture (pure helpers), methods (happy path, idempotent re-run, dryRun-mutates-nothing, per-group search failure isolation, seeder floor, empty-group rejection) and adversarial (HTML-injection escaping at the Telegram subprocess boundary, group impersonation outside the credit bracket, credential-leak sweep) suites. The sanity: model exposes exactly the N documented methods pin moved 4 → 5, and the two NyaaHit literal builders in the existing suites gained sizeBytes.

Modified 1 models

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.

2026.06.07.1

Merge pull request #38 from umag/feat/anime-cron

Add @magistr/anime-cron: AniList+Nyaa+SeaDex anime automation pipeline

05Stats
A
100 / 100
Downloads
0
Archive size
39.5 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
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License declared1/1earned
  • Verified public repository2/2earned
06Platforms
07Labels