Skip to content

Add s.to catalogue support alongside AniWorld for dual-site content brokering#8

Merged
Zzackllack merged 35 commits intomainfrom
copilot/add-s-to-catalogue-support
Oct 21, 2025
Merged

Add s.to catalogue support alongside AniWorld for dual-site content brokering#8
Zzackllack merged 35 commits intomainfrom
copilot/add-s-to-catalogue-support

Conversation

Copy link
Contributor

Copilot AI commented Oct 18, 2025

Overview

This PR extends AniBridge to support both aniworld.to (anime) and s.to (series) catalogues simultaneously, allowing Sonarr/Prowlarr to seamlessly query content from both sources through a unified interface.

Problem Statement

Previously, AniBridge was hardcoded to only query aniworld.to, limiting it to anime content. The upstream aniworld library supports multiple sites including s.to (for general series), but AniBridge never exposed this capability. Users wanting series content had no way to access s.to's catalogue through the existing Torznab/qBittorrent APIs.

Solution

Implemented comprehensive multi-site support by:

1. Configuration Layer (app/config.py)

Added site-specific configuration with sensible defaults:

  • CATALOG_SITES - comma-separated list of enabled sites (default: "aniworld.to,s.to")
  • Per-site alphabet URLs, cache files, refresh intervals, and release groups
  • Fully backward compatible - defaults to aniworld.to only if not configured

2. Title Resolution (app/utils/title_resolver.py)

Refactored to maintain independent indices per site:

  • slug_from_query() now searches across all enabled catalogues
  • Returns (site, slug) tuples instead of just slug
  • Per-site caching with independent TTL management
  • Site-specific regex patterns for slug extraction

3. Download Pipeline (app/core/downloader.py, app/utils/probe_quality.py)

Extended to support site-specific episodes:

  • Added "English Dub" language support (required for s.to)
  • Site parameter threaded through build_episode(), download_episode(), and quality probing
  • Proper base URL selection based on originating catalogue

4. Metadata & Naming (app/utils/naming.py, app/utils/magnet.py)

Enhanced to preserve source information:

  • Site-specific release groups in filenames (configurable via RELEASE_GROUP_ANIWORLD, RELEASE_GROUP_STO)
  • Magnet URIs use aw_* prefix for aniworld.to, sto_* for s.to content
  • Site metadata included in magnet parameters
  • Backward compatible parsing supports legacy aw_* only magnets

5. Database Schema (app/db/models.py)

Added source tracking throughout:

  • Job.source_site - tracks which catalogue originated the download (indexed)
  • EpisodeAvailability.site - now part of primary key for proper per-site caching
  • ClientTask.site - preserves source in qBittorrent shim state
  • All CRUD functions updated with site parameter defaults

6. qBittorrent API (app/api/qbittorrent/torrents.py, app/core/scheduler.py)

Updated to handle multi-site magnets:

  • Automatic site detection from magnet parameter prefixes (aw_* vs sto_*)
  • Site propagation through job scheduling and execution
  • Source tracking persisted in jobs and client tasks

7. Torznab API (app/api/torznab/api.py, app/api/torznab/utils.py)

Implemented multi-catalogue search fanout:

  • Searches across all enabled sites for each query
  • Site-appropriate language defaults (e.g., "English Dub" for s.to, "German Dub" for aniworld.to)
  • GUID prefixes indicate source (aw: vs sto:)
  • Results include site metadata for proper downstream handling

Usage Example

Enable both catalogues by setting environment variable:

CATALOG_SITES=aniworld.to,s.to

Or restrict to specific catalogue:

# Anime only
CATALOG_SITES=aniworld.to

# Series only
CATALOG_SITES=s.to

Magnet URI Format

aniworld.to content:

magnet:?xt=urn:btih:...&aw_slug=slug&aw_s=1&aw_e=1&aw_lang=German+Dub&aw_site=aniworld.to

s.to content:

magnet:?xt=urn:btih:...&sto_slug=slug&sto_s=1&sto_e=1&sto_lang=English+Dub&sto_site=s.to

Search Flow

  1. Query arrives via Torznab or qBittorrent API
  2. slug_from_query() searches all enabled catalogues in parallel
  3. Returns (site, slug) tuple for best match
  4. Site information propagates through entire download pipeline
  5. Magnet generated with appropriate prefix and metadata
  6. Job tracked with source_site in database

Testing

  • ✅ All 32 existing tests passing (100% success rate)
  • ✅ Updated tests to handle (site, slug) tuple returns
  • ✅ CodeQL security scan: 0 vulnerabilities detected
  • ✅ Backward compatibility verified with legacy magnet URIs

Backward Compatibility

Fully backward compatible with existing deployments:

  • Defaults to aniworld.to only if CATALOG_SITES not configured
  • Legacy aw_* magnet parameters still work
  • Existing database entries get default source_site="aniworld.to"
  • No breaking changes to existing API contracts
  • Database schema updates automatically on restart

Migration Notes

For existing deployments:

  • No manual migration required
  • Database schema updates happen automatically
  • Old data receives sensible defaults
  • Simply add CATALOG_SITES=aniworld.to,s.to to environment to enable dual catalogues

Future Enhancements

While this implementation is production-ready, potential follow-up improvements include:

  • Documentation updates for new environment variables
  • Site-specific integration tests (requires live s.to access)
  • Metrics tracking per-site query distribution
  • Site priority/ordering configuration options

Security

CodeQL Analysis: ✅ PASSED

  • No SQL injection vulnerabilities
  • No XSS vulnerabilities
  • Proper input validation and sanitization
  • Safe site parameter handling throughout

This implementation successfully delivers the requested multi-site capability while maintaining full backward compatibility and production-grade quality.

Closes #6

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/phoenixthrush/AniWorld-Downloader/releases/latest
    • Triggering command: python -m pytest -xvs (http block)
    • Triggering command: python -m pytest tests/test_models.py -xvs (http block)
    • Triggering command: python -m pytest tests/test_qbittorrent_torrents.py -xvs (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Add s.to catalogue support alongside AniWorld</issue_title>
<issue_description>

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

Fixes #6


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Summary by CodeRabbit

  • New Features
    • Multi-site support: items, magnets, downloads, naming and availability now include site context.
    • Magnets accept two site-specific prefixes and validate mixed/invalid prefix combos.
    • Per-site defaults: language preferences, release-group selection, and title resolution.
    • Job tracking: downloads record source site; site flows through scheduling and naming.
  • Other
    • Language list expanded (adds "English Dub" aliases).
  • Tests
    • Added/updated tests for multi-site magnets, slug/title resolution and end-to-end scheduling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 18, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds multi-site (aniworld.to / s.to) support across config, title resolution, torznab API, magnet handling, downloader/scheduler, DB models, and naming so the originating site is detected, propagated, persisted, and encoded in magnets/release names.

Changes

Cohort / File(s) Summary
Configuration
app/config.py
Adds multi-site catalog support: CATALOG_SITES, per-site base URLs, alphabet endpoints, per-site refresh hours, release-group constants, and CATALOG_SITE_CONFIGS.
Title resolution & indices
app/utils/title_resolver.py
Refactors to per-site indices, per-site caches/TTLs, site-aware parsing and slug resolution; adds site-parameterized helpers and slug_from_query returning (site, slug).
Torznab API & utils
app/api/torznab/api.py, app/api/torznab/utils.py
Introduces _default_languages_for_site; uses (site, slug) from resolver; propagates site through language defaults, GUIDs, probe/upsert/build_magnet, RSS item creation, and logs.
Magnet handling
app/utils/magnet.py
build_magnet and parse_magnet become site-aware: dynamic prefixes (aw_ / sto_), include {prefix}_site, validate prefix consistency, and accept site parameter.
Naming & probe quality
app/utils/naming.py, app/utils/probe_quality.py
build_release_name / rename_to_release and probe_episode_quality accept site, apply site-specific release_group/defaults, and propagate site into title resolution and logs.
Downloader & scheduler
app/core/downloader.py, app/core/scheduler.py
build_episode / download_episode gain site param and pass it through; Language literal extended (adds "English Dub" and aliases); scheduler reads site from requests, persists source_site, and forwards site to downloader and job updates.
Database models & migrations
app/db/models.py
Adds source_site to Job; adds site PK component to EpisodeAvailability and site to ClientTask; extends create_job/upsert/get/list/upsert_client_task signatures; includes SQLite migration helper to add site.
qBittorrent API
app/api/qbittorrent/torrents.py
Magnet parsing now detects prefix-derived site, extracts prefix-specific fields (slug/season/episode/lang/provider/site), includes site in schedule payload and logs, and passes site to schedule_download / upsert_client_task.
Utils / other
app/utils/naming.py, app/utils/probe_quality.py, app/utils/magnet.py
Various helpers adjusted to accept/return site, site-aware defaults and logging updated.
Tests
tests/test_torznab.py, tests/test_torznab_utils.py, tests/test_magnet.py, tests/test_qbit_torrents_add.py, tests/test_qbittorrent_torrents.py
Tests made site-aware (monkeypatches accept/return site), new test for mixed-prefix magnet error, and tests for adding aw_/sto_ magnets via API.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Sonarr/Prowlarr
    participant Torznab as Torznab API
    participant Resolver as Title Resolver
    participant Probe as Probe/Downloader
    participant DB as Database
    Note over Torznab,Resolver: Search request flow (site-detection)
    Client->>Torznab: search(query)
    Torznab->>Resolver: slug_from_query(query)
    Resolver->>Resolver: load_or_refresh_index(site?)  %% blue
    Resolver-->>Torznab: (site, slug)
    Torznab->>Probe: probe_episode_quality(slug, season, episode, site)
    Probe->>DB: list_available_languages_cached(slug,..., site)
    DB-->>Probe: languages (site-scoped)
    Probe-->>Torznab: quality info (site)
    Torznab->>Torznab: build_magnet(..., site)
    Torznab-->>Client: RSS item (GUID & magnet with aw_/sto_ params)
    Client->>Torznab: download(magnet)
    Torznab->>Torznab: parse_magnet -> detect prefix -> site
    Torznab->>DB: create_job(source_site=site)
    Torznab->>Probe: schedule_download(req with site)
    Probe->>DB: upsert_availability(..., site)
    DB-->>Probe: ack
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰
I hopped from aw burrow to sto hill,
with slugs and seasons snug and still.
Two prefixes stitched into each thread —
aw_ and sto_ where releases spread.
I nudge the bridge; both warrens fill.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.68% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "Add s.to catalogue support alongside AniWorld for dual-site content brokering" clearly and concisely summarizes the primary change: extending AniBridge to support querying multiple catalogues simultaneously. The title is specific enough that a teammate scanning commit history would immediately understand the main feature being added, avoids vague terms, and directly corresponds to the comprehensive multi-site changes evident throughout the codebase. The phrasing is direct and professional without unnecessary embellishment.
Linked Issues Check ✅ Passed The pull request successfully addresses all primary coding-related objectives from issue #6. Configuration layer support is implemented via CATALOG_SITES and per-site defaults [app/config.py]. Dual title indices with per-site caching and (site, slug) tuple returns are implemented [app/utils/title_resolver.py]. Site-aware request pipelines that fan out across catalogues are added to both Torznab and qBittorrent APIs [app/api/torznab/api.py, app/api/qbittorrent/torrents.py]. The site parameter is threaded through build_episode, download_episode, and quality probing [app/core/downloader.py, app/utils/probe_quality.py]. Metadata and magnet generation now encode site information [app/utils/magnet.py, app/utils/naming.py]. Database schema has been updated with source tracking [app/db/models.py]. Documentation updates are deferred as future enhancement work but are noted in the PR description as out-of-scope for this implementation.
Out of Scope Changes Check ✅ Passed All code changes are directly scoped to implementing the multi-site catalogue support objective outlined in issue #6. The modifications systematically add site-awareness throughout the configuration, title resolution, API layers, downloader pipeline, database schema, and metadata generation. Changes to the Language literal to include "English Dub" are necessary for s.to language support. Test updates to handle (site, slug) tuples are essential for validating the new API. Database migration additions are required infrastructure for schema evolution. No extraneous changes, unrelated refactoring, or tangential modifications are present.
Description Check ✅ Passed The pull request description is comprehensive and well-structured, exceeding the template requirements in both depth and clarity. It includes a detailed "Overview" and "Problem Statement," a "Solution" section with seven logically organized subsections (Configuration, Title Resolution, Download Pipeline, Metadata, Database, APIs, and Testing), practical "Usage Example" and "Magnet URI Format" sections, a "Search Flow" walkthrough, documented "Testing" results, "Backward Compatibility" assurances, and "Migration Notes." While the template asks for a "Type of Change" checkbox and explicit testing checklists, the PR description effectively covers all required information through narrative and structured sections. The description demonstrates clear understanding of the changes and their implications.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI and others added 4 commits October 18, 2025 22:19
- Add CATALOG_SITES config with site-specific settings for aniworld.to and s.to
- Update downloader.py to support "English Dub" language and site parameter
- Extend title_resolver.py with multi-site index caching and slug resolution
- Update naming.py with site-aware release groups and "English Dub" support
- Enhance magnet.py to include site metadata in URIs (aw_* vs sto_* prefixes)
- All backward compatible with existing aw_* magnet parameters

Co-authored-by: Zzackllack <149129501+Zzackllack@users.noreply.github.com>
- Add source_site field to Job, EpisodeAvailability, and ClientTask models
- Update CRUD functions to handle site parameter
- Site becomes part of EpisodeAvailability primary key for proper caching
- All functions default to 'aniworld.to' for backward compatibility

Co-authored-by: Zzackllack <149129501+Zzackllack@users.noreply.github.com>
- Extract site from magnet parameters (aw_* vs sto_* prefixes)
- Pass site parameter through schedule_download and job execution
- Update scheduler to track source_site in jobs
- Backward compatible with legacy aw_* only magnets
- All tests passing (32/32)

Co-authored-by: Zzackllack <149129501+Zzackllack@users.noreply.github.com>
- Update Torznab search and tvsearch endpoints to handle (site, slug) tuples
- Add site parameter to probe_episode_quality
- Update availability caching with site parameter
- Site-specific language defaults (English Dub for s.to)
- Site-aware GUID prefixes (aw: vs sto:)
- Update tests to match new multi-site API
- All 32 tests passing

Co-authored-by: Zzackllack <149129501+Zzackllack@users.noreply.github.com>
Copilot AI changed the title [WIP] Add S.to catalogue support alongside AniWorld Add s.to catalogue support alongside AniWorld for dual-site content brokering Oct 18, 2025
Copilot AI requested a review from Zzackllack October 18, 2025 22:39
@Zzackllack Zzackllack requested a review from Copilot October 19, 2025 00:00
@Zzackllack

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@Zzackllack Zzackllack marked this pull request as ready for review October 19, 2025 00:01

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as outdated.

@Zzackllack Zzackllack added the enhancement New feature or request label Oct 19, 2025
@Zzackllack

This comment was marked as duplicate.

@coderabbitai

This comment was marked as duplicate.

@coderabbitai

This comment was marked as resolved.

Docstrings generation was requested by @Zzackllack.

* #8 (comment)

The following files were modified:

* `app/api/qbittorrent/torrents.py`
* `app/api/torznab/api.py`
* `app/api/torznab/utils.py`
* `app/core/downloader.py`
* `app/core/scheduler.py`
* `app/db/models.py`
* `app/utils/magnet.py`
* `app/utils/naming.py`
* `app/utils/probe_quality.py`
* `app/utils/title_resolver.py`
* `tests/test_torznab_utils.py`
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 20, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
🔵 In progress
View logs
anibridge-docs bc6a198 Oct 21 2025, 11:09 PM

@Zzackllack

This comment was marked as duplicate.

@coderabbitai

This comment was marked as duplicate.

Docstrings generation was requested by @Zzackllack.

* #8 (comment)

The following files were modified:

* `app/api/torznab/api.py`
* `app/api/torznab/utils.py`
* `app/core/downloader.py`
* `app/core/scheduler.py`
* `app/db/models.py`
* `app/utils/magnet.py`
* `app/utils/probe_quality.py`
* `app/utils/title_resolver.py`
* `tests/test_qbittorrent_torrents.py`
* `tests/test_torznab_utils.py`
@Zzackllack
Copy link
Owner

@codex Update the example env file and also update all respective documentations sites for this also like advertise that we now not only support aniworld.to but also s.to/serienstream.to. Then rate this pr after all these changes/commits, is it ready to be merged into main/for production or do we need to change something (how critical are these changes, if any)

@chatgpt-codex-connector
Copy link

Summary

Testing

  • ⚠️ pytest -q (fails: ModuleNotFoundError: No module named 'fastapi')

PR readiness: The requested documentation and example environment updates are in place, and the changes are low-risk content edits. Aside from the local environment missing fastapi, there are no outstanding issues—this PR is ready to merge once tests run in a configured environment.

View task →

Copy link
Owner

@Zzackllack Zzackllack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now, after so many changes/commits, ready for production/merge onto main I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add s.to catalogue support alongside AniWorld

3 participants