Skip to content

Add s.to catalogue support alongside AniWorld #6

@Zzackllack

Description

@Zzackllack

Summary

  • Extend AniBridge so it can broker both anime (aniworld.to) and general-series (s.to) catalogues at the same time.
  • Teach the HTTP, Torznab, and qBittorrent layers to fan out queries to both sites, merge results, and hand Sonarr/Prowlarr back whichever entries match the requested slug/keywords.
  • Reuse the upstream aniworld library's multi-site features, but expose the required site hints, language maps, and metadata so the bridge can choose the correct catalogue per request.

Context & Findings

  • Different content domains: In the upstream CLI, aniworld.to targets anime while s.to fronts series. AniBridge currently assumes every inbound request is anime, so all slug resolution, availability caching, and naming is hard-coded to /anime/stream/... anchors (app/utils/title_resolver.py:18-124). We need dual indices: /anime/stream/<slug> for AniWorld and /serie/stream/<slug> for s.to.
  • Downloader site defaults: build_episode never passes a site argument when constructing aniworld.models.Episode, so the library falls back to aniworld.to (app/core/downloader.py:83). The literal Language union also omits "English Dub", which is a valid s.to language (aniworld.config.LANGUAGE_CODES_STO, .venv/lib/python3.13/site-packages/aniworld/config.py:37-43).
  • Absolute episode helper: resolve_absolute_episode calls get_season_episode_count(slug) with no base URL. The helper distinguishes sites by inspecting the link it is called with (aniworld.common.common:get_season_episode_count). Without a site-aware wrapper AniBridge will always hit the AniWorld endpoint even when the slug is an s.to show.
  • Library readiness: aniworld.models.Anime / Episode accept site="s.to" (and adjust base URL + stream path accordingly) and share provider extraction logic across both (.venv/lib/python3.13/site-packages/aniworld/models.py:68-520). We simply never surface that knob.
  • Naming & metadata: The release naming helper hard-codes RELEASE_GROUP=aniworld and magnet payload prefixes (aw_*). If we aggregate both catalogues the metadata should either encode the originating site, or at minimum stop labelling non-anime releases as AniWorld-only (app/utils/naming.py:133-180, app/utils/magnet.py:14-75).
  • Search orchestration gap: Torznab and qBittorrent flows currently assume a single slug → provider pipeline. There is no abstraction for "search both catalogues and merge"; we need to design the fan-out/fan-in layer (sequential vs parallel, conflict resolution when both return hits, caching strategy, etc.).

Proposed Changes

  1. Configuration surface
    • Add a CATALOG_SITES (default aniworld.to,s.to) env variable plus optional mirrors (base URL overrides for each). Provide per-site defaults for alphabet/search endpoints, release groups, and language maps.
  2. Dual title indices
    • Refactor title_resolver so it can fetch and cache per-site indices (e.g., aniworld_titles, sto_titles). Store (site, slug) pairs and extend _slug_from_query to consider both sets so Sonarr lookups can locate shows from either catalogue.
  3. Site-aware request pipeline
    • Update torznab search handlers to fan out to each enabled site. Decide on strategy: run lookups concurrently (async tasks) or sequentially with early exit when the first positive match appears. Preserve the site in the returned payload so the download scheduler knows which Episode(site=...) to instantiate.
    • When both sites return hits for the same query, dedupe by canonical title/season/episode and decide ordering (e.g., prefer explicit matches over fuzzy ones, allow configuration to prioritise anime vs series).
  4. Downloader & helpers
    • Thread the selected site through build_episode, get_direct_url_with_fallback, probe_episode_quality, and absolute-number helpers. Expand the Language literal and rename variables (e.g., ANIWORLD_*) that are now site-agnostic.
    • Persist availability/job metadata with a source_site column so qBittorrent sync responses report the right origin.
  5. Metadata & naming
    • Extend build_release_name/build_magnet to encode site-aware tags (source=aniworld|sto or site-specific release groups). Update docs so users can configure distinct group suffixes per site.
  6. API/CLI surface
    • Expose an optional query parameter or environment flag so operators can toggle one catalogue off if required (rate limits/legal reasons) without redeploying a different image.
  7. Docs & onboarding
    • Update README, docs (docs/src/guide/configuration.md, docs/src/api/environment.md) and onboarding checklists to explain the dual catalogue behaviour, configuration knobs, and new terminology (anime vs series).

Testing Ideas

  • Unit tests for the new title_resolver that feed sample AniWorld and s.to HTML and ensure queries resolve to the correct (site, slug) pair.
  • Downloader tests that instantiate Episode(site="s.to"), verify language probing includes "English Dub", and confirm provider fallbacks still operate.
  • Torznab integration tests that simulate:
    • Query resolving only on AniWorld
    • Query resolving only on s.to
    • Query resolving on both; validate dedupe/ordering logic and that magnets carry site metadata.
  • Regression tests for absolute-number mapping when the site is s.to (ensure the helper calls the correct base URL).
  • Tests ensuring qBittorrent sync endpoint returns site-qualified jobs and that CLI jobs persist the new source_site field.

Open Questions

  • Search orchestration: Should we query both catalogues in parallel for every request, or first check the site most likely to contain the content (e.g., anime → AniWorld, live action → s.to) based on heuristics/config? Provide a knob for sequential vs parallel to balance latency vs rate limits.
  • Result collisions: When both sites return plausible matches (e.g., a live-action adaptation on s.to and its anime counterpart on AniWorld), how should Sonarr see them? Present both entries, prefer one, or allow per-series configuration?
  • Base URLs / mirrors: s.to currently ships as http://186.2.175.5 in the library. Do we want to expose mirror configuration (including HTTPS) by default to avoid hardcoding an IP that may change?
  • Language defaults: How should we expose per-site language defaults to users (e.g., prefer English Dub on s.to vs German Dub on AniWorld) without forcing global overrides?
  • Magnet payload naming: Do downstream consumers rely on the aw_* magnet params? If so, do we add parallel sto_* fields or transition to a neutral namespace while preserving backwards compatibility?

Metadata

Metadata

Labels

documentationImprovements or additions to documentationenhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions