Files
Aniworld/docs/instructions.md

20 KiB
Raw Blame History

Aniworld Web Application Development Instructions

This document provides detailed tasks for AI agents to implement a modern web application for the Aniworld anime download manager. All tasks should follow the coding guidelines specified in the project's copilot instructions.

Project Overview

The goal is to create a FastAPI-based web application that provides a modern interface for the existing Aniworld anime download functionality. The core anime logic should remain in SeriesApp.py while the web layer provides REST API endpoints and a responsive UI.

Architecture Principles

  • Single Responsibility: Each file/class has one clear purpose
  • Dependency Injection: Use FastAPI's dependency system
  • Clean Separation: Web layer calls core logic, never the reverse
  • File Size Limit: Maximum 500 lines per file
  • Type Hints: Use comprehensive type annotations
  • Error Handling: Proper exception handling and logging

Additional Implementation Guidelines

Code Style and Standards

  • Type Hints: Use comprehensive type annotations throughout all modules
  • Docstrings: Follow PEP 257 for function and class documentation
  • Error Handling: Implement custom exception classes with meaningful messages
  • Logging: Use structured logging with appropriate log levels
  • Security: Validate all inputs and sanitize outputs
  • Performance: Use async/await patterns for I/O operations

📞 Escalation

If you encounter:

  • Architecture issues requiring design decisions
  • Tests that conflict with documented requirements
  • Breaking changes needed
  • Unclear requirements or expectations

Document the issue and escalate rather than guessing.


<EFBFBD> Credentials

Admin Login:

  • Username: admin
  • Password: Hallo123!

<EFBFBD>📚 Helpful Commands

# Run all tests
conda run -n AniWorld python -m pytest tests/ -v --tb=short

# Run specific test file
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py -v

# Run specific test class
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService -v

# Run specific test
conda run -n AniWorld python -m pytest tests/unit/test_websocket_service.py::TestWebSocketService::test_broadcast_download_progress -v

# Run with extra verbosity
conda run -n AniWorld python -m pytest tests/ -vv

# Run with full traceback
conda run -n AniWorld python -m pytest tests/ -v --tb=long

# Run and stop at first failure
conda run -n AniWorld python -m pytest tests/ -v -x

# Run tests matching pattern
conda run -n AniWorld python -m pytest tests/ -v -k "auth"

# Show all print statements
conda run -n AniWorld python -m pytest tests/ -v -s

#Run app
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload

Implementation Notes

  1. Incremental Development: Implement features incrementally, testing each component thoroughly before moving to the next
  2. Code Review: Review all generated code for adherence to project standards
  3. Documentation: Document all public APIs and complex logic
  4. Testing: Maintain test coverage above 80% for all new code
  5. Performance: Profile and optimize critical paths, especially download and streaming operations
  6. Security: Regular security audits and dependency updates
  7. Monitoring: Implement comprehensive monitoring and alerting
  8. Maintenance: Plan for regular maintenance and updates

Task Completion Checklist

For each task completed:

  • Implementation follows coding standards
  • Unit tests written and passing
  • Integration tests passing
  • Documentation updated
  • Error handling implemented
  • Logging added
  • Security considerations addressed
  • Performance validated
  • Code reviewed
  • Task marked as complete in instructions.md
  • Infrastructure.md updated and other docs
  • Changes committed to git; keep your messages in git short and clear
  • Take the next task

TODO List:


Task 0: Populate all required NFO tags during normal creation

Goal: Ensure create_tvshow_nfo() writes every required tag from the start, so newly created NFOs are complete and never require a repair pass. All missing fields must be mapped inside _tmdb_to_nfo_model() in src/core/services/nfo_service.py.

Required tags currently missing from creation path:

Field Current state Fix
originaltitle Never set tmdb_data.get("original_name")
year Never set Extract 4-digit year from first_air_date
plot May be empty tmdb_data.get("overview")
runtime Never set tmdb_data.get("episode_run_time", [None])[0]
premiered Never set tmdb_data.get("first_air_date")
status Never set tmdb_data.get("status")
imdbid Only imdb_id written Add explicit imdbid field alongside imdb_id
genre Not checked Map each tmdb_data["genres"] entry
studio Not checked Map each tmdb_data["networks"] entry
country Never set tmdb_data.get("origin_country", [])
actor Not checked Map each credits["cast"] entry
watched Pydantic default Set explicitly to False
tagline Never set tmdb_data.get("tagline")
outline Never set Set equal to plot (same overview source)
sorttitle Never set Set equal to title
dateadded Never set datetime.now().strftime("%Y-%m-%d %H:%M:%S")
mpaa Never set Extract US rating from content_ratings via helper

Implementation steps:

  1. Open src/core/services/nfo_service.py — read the full file before making any changes
  2. In _tmdb_to_nfo_model(), add assignments for all fields listed above and pass them to TVShowNFO(...)
  3. Add _extract_mpaa_rating(content_ratings) helper — mirrors existing _extract_fsk_rating but looks for country code "US"; refactor both into _extract_rating_by_country(content_ratings, country_code: str) to eliminate duplication
  4. Add from datetime import datetime import if not already present
  5. No changes needed to update_tvshow_nfo() — it re-calls _tmdb_to_nfo_model() already
  6. If TVShowNFO Pydantic model is missing fields (originaltitle, outline, sorttitle, dateadded, mpaa, country, watched), add them with Optional[...] defaults of None / False
  7. Open src/core/utils/nfo_generator.py — read the full file before making changes
  8. Confirm _add_element calls exist (ungated) for: tagline, outline, sorttitle, dateadded, originaltitle, year, runtime, premiered, status, country, imdbid
  9. Verify mpaa fallback: prefer fsk when nfo_prefer_fsk_rating=True AND fsk is not None; otherwise write mpaa
  10. Verify _add_element does NOT suppress "false" strings — watched=False must appear as <watched>false</watched>
  11. Keep both files under 500 lines; if nfo_service.py exceeds limit, extract _tmdb_to_nfo_model into src/core/utils/nfo_mapper.py

Validation steps:

  • Read the updated _tmdb_to_nfo_model() and confirm all 17 fields are explicitly assigned
  • Read the updated nfo_generator.py and confirm every field has a corresponding _add_element call
  • Delete a real series' tvshow.nfo, trigger creation, open result and cross-check every tag against tvshow.nfo.good
  • Confirm no wildcard imports added (from module import *)
  • Confirm both files stay under 500 lines (wc -l src/core/services/nfo_service.py src/core/utils/nfo_generator.py)

Test steps — create tests/unit/test_nfo_creation_tags.py:

  • test_tmdb_to_nfo_model_sets_originaltitle
  • test_tmdb_to_nfo_model_sets_year_from_first_air_date
  • test_tmdb_to_nfo_model_sets_plot_from_overview
  • test_tmdb_to_nfo_model_sets_runtime
  • test_tmdb_to_nfo_model_sets_premiered
  • test_tmdb_to_nfo_model_sets_status
  • test_tmdb_to_nfo_model_sets_imdbid
  • test_tmdb_to_nfo_model_sets_genres
  • test_tmdb_to_nfo_model_sets_studios_from_networks
  • test_tmdb_to_nfo_model_sets_country
  • test_tmdb_to_nfo_model_sets_actors
  • test_tmdb_to_nfo_model_sets_watched_false
  • test_tmdb_to_nfo_model_sets_tagline
  • test_tmdb_to_nfo_model_sets_outline_from_overview
  • test_tmdb_to_nfo_model_sets_sorttitle_from_name
  • test_tmdb_to_nfo_model_sets_dateadded
  • test_tmdb_to_nfo_model_sets_mpaa_from_content_ratings
  • test_extract_rating_by_country_returns_us_rating
  • test_extract_rating_by_country_returns_none_when_no_match
  • test_extract_rating_by_country_handles_empty_results
  • test_generate_nfo_includes_all_required_tags — generate XML from a fully-populated TVShowNFO; parse with lxml; assert every required tag present
  • test_generate_nfo_writes_watched_false — assert <watched>false</watched> not omitted
  • test_generate_nfo_minimal_model_does_not_crash
  • test_generate_nfo_writes_fsk_over_mpaa_when_prefer_fsk — both set, prefer_fsk=True → FSK value written
  • test_generate_nfo_writes_mpaa_when_no_fskfsk=None, mpaa="TV-14", prefer_fsk=True → <mpaa>TV-14</mpaa>
conda run -n AniWorld python -m pytest tests/unit/test_nfo_creation_tags.py -v --tb=short
conda run -n AniWorld python -m pytest tests/ -v --tb=short

Documentation steps:

  • Update docs/NFO_GUIDE.md — add "Tag Reference" table: tag → TMDB source field → optional/required
  • Add entry in docs/CHANGELOG.md under [Unreleased]
  • Update docs/ARCHITECTURE.md under the NFO services section to reflect new fields and the _extract_rating_by_country helper

Commit step:

git add src/core/services/nfo_service.py src/core/utils/nfo_generator.py tests/unit/test_nfo_creation_tags.py docs/
git commit -m "feat: write all required NFO tags on creation"

Task 1: Create NfoRepairService — detect missing/empty NFO tags

Goal: Implement a service that parses an existing tvshow.nfo file and determines whether required tags are missing or empty, compared to the Kodi/Jellyfin standard defined in tvshow.nfo.good.

Required tags to check (must be present and non-empty):

  • title, originaltitle, year, plot, runtime, premiered, status
  • imdbid (not just imdb_id)
  • At least one genre, studio, country, actor/name
  • watched

Implementation steps:

  1. Create src/core/services/nfo_repair_service.py (max 500 lines, type hints, docstrings)
  2. Define REQUIRED_TAGS: dict[str, str] — XPath expression → human-readable label for each required tag
  3. Implement def parse_nfo_tags(nfo_path: Path) -> dict — uses lxml.etree.parse(), returns tag→value(s), silent on parse errors
  4. Implement def find_missing_tags(nfo_path: Path) -> list[str] — calls parse_nfo_tags(), returns list of tag names that are absent or have empty text
  5. Implement def nfo_needs_repair(nfo_path: Path) -> bool — returns bool(find_missing_tags(nfo_path))
  6. Implement class NfoRepairService:
    • __init__(self, nfo_service: NFOService) — inject existing NFOService
    • async def repair_series(self, series_path: Path, series_name: str) -> bool — calls nfo_needs_repair(), logs which tags are missing, calls nfo_service.update_tvshow_nfo(series_path, series_name), returns True if repair was triggered
  7. Add structured logging at INFO level for: repair triggered (with list of missing tags), repair skipped, repair completed

Validation steps:

  • Manually verify find_missing_tags() returns correct results when given tvshow.nfo.bad as input
  • Manually verify find_missing_tags() returns an empty list when given tvshow.nfo.good as input
  • Check that no imports are wildcard (from module import *)
  • Check file stays under 500 lines

Test steps — create tests/unit/test_nfo_repair_service.py:

  • test_find_missing_tags_with_bad_nfo — use fixture copying tvshow.nfo.bad; assert all 13 expected missing tags are returned
  • test_find_missing_tags_with_good_nfo — use fixture copying tvshow.nfo.good; assert empty list returned
  • test_nfo_needs_repair_returns_true_for_bad_nfo
  • test_nfo_needs_repair_returns_false_for_good_nfo
  • test_repair_series_calls_update_when_nfo_needs_repair — mock NFOService.update_tvshow_nfo; assert it is called exactly once
  • test_repair_series_skips_when_nfo_is_complete — mock NFOService.update_tvshow_nfo; assert it is NOT called
  • test_parse_nfo_tags_handles_missing_file_gracefully — pass nonexistent path; expect empty dict or graceful return
  • test_parse_nfo_tags_handles_malformed_xml_gracefully — pass file with invalid XML; expect graceful return

Run tests:

conda run -n AniWorld python -m pytest tests/unit/test_nfo_repair_service.py -v --tb=short

Documentation steps:

  • Add entry for NfoRepairService in docs/ARCHITECTURE.md under the NFO services section
  • Add entry in docs/CHANGELOG.md under a new [Unreleased] section

Commit step:

git add src/core/services/nfo_repair_service.py tests/unit/test_nfo_repair_service.py docs/
git commit -m "feat: add NfoRepairService for missing NFO tag detection"

Task 2: Add perform_nfo_repair_scan() startup hook in initialization_service.py

Goal: Add a new async function that runs every startup (not run-once), scans all series folders for NFOs with missing tags, and queues repair tasks via BackgroundLoaderService.

Implementation steps:

  1. Open src/server/services/initialization_service.py
  2. Add import for NfoRepairService from src.core.services.nfo_repair_service
  3. Implement async def perform_nfo_repair_scan(background_loader=None) -> None:
    • Guard: if not settings.tmdb_api_key → log warning and return early
    • Guard: if not settings.anime_directory → log warning and return early
    • Create NfoRepairService instance using NFOServiceFactory (follow same pattern as existing perform_nfo_scan_if_needed())
    • Iterate all subdirectories in settings.anime_directory
    • For each series folder that contains a tvshow.nfo: call nfo_needs_repair(nfo_path)
    • If repair needed: queue via background_loader.add_series_loading_task() (non-blocking), or call repair_service.repair_series() directly if no background_loader provided
    • Log summary at INFO level: "NFO repair scan complete: X of Y series queued for repair"
  4. Keep function under 60 lines; extract helpers if needed

Validation steps:

  • Read the function and confirm it does NOT use a run-once DB flag (unlike perform_nfo_scan_if_needed)
  • Confirm both guards (tmdb_api_key, anime_directory) are present
  • Confirm the function is async and uses await where needed
  • Confirm initialization_service.py stays under 500 lines after the addition

Test steps — add to tests/unit/test_initialization_service.py (create if not exists):

  • test_perform_nfo_repair_scan_skips_without_tmdb_api_key — patch settings.tmdb_api_key = ""; assert no repair is attempted
  • test_perform_nfo_repair_scan_skips_without_anime_directory — patch settings.anime_directory = ""; assert no repair is attempted
  • test_perform_nfo_repair_scan_queues_deficient_series — create temp dir with one bad NFO; mock NfoRepairService.nfo_needs_repair returning True; assert background_loader is called
  • test_perform_nfo_repair_scan_skips_complete_series — create temp dir with one good NFO; mock nfo_needs_repair returning False; assert background_loader NOT called

Run tests:

conda run -n AniWorld python -m pytest tests/unit/test_initialization_service.py -v --tb=short

Documentation steps:

  • Update docs/ARCHITECTURE.md startup sequence diagram/description to include the new perform_nfo_repair_scan step
  • Add note in docs/CONFIGURATION.md that tmdb_api_key is required for the repair scan

Commit step:

git add src/server/services/initialization_service.py tests/unit/test_initialization_service.py docs/
git commit -m "feat: add perform_nfo_repair_scan startup hook"

Task 3: Wire perform_nfo_repair_scan into fastapi_app.py lifespan

Goal: Call the new repair scan on every application startup, after the existing NFO creation scan.

Implementation steps:

  1. Open src/server/fastapi_app.py
  2. Add perform_nfo_repair_scan to the existing import from src.server.services.initialization_service
  3. In the lifespan() async context manager, after the line await perform_nfo_scan_if_needed(), add:
    await perform_nfo_repair_scan(background_loader)
    
    (pass the background_loader instance so repairs are queued non-blocking)
  4. Confirm the call is inside the startup block (before yield), not in the shutdown block

Validation steps:

  • Read fastapi_app.py and confirm ordering: perform_nfo_scan_if_needed runs before perform_nfo_repair_scan
  • Confirm background_loader is instantiated before the repair scan call
  • Start the server locally and check logs for "NFO repair scan" log lines
  • Confirm app boot completes without errors even if TMDB key is missing

Test steps — add to tests/integration/test_nfo_repair_startup.py (new file):

  • test_startup_triggers_nfo_repair_scan — use TestClient with lifespan; mock perform_nfo_repair_scan; assert it was called once during startup
  • test_startup_repair_scan_receives_background_loader — assert perform_nfo_repair_scan is called with a non-None background_loader argument

Run tests:

conda run -n AniWorld python -m pytest tests/integration/test_nfo_repair_startup.py -v --tb=short

Full regression test — run all tests and confirm nothing broken:

conda run -n AniWorld python -m pytest tests/ -v --tb=short

Documentation steps:

  • Update startup sequence in docs/ARCHITECTURE.md to add the new step after perform_nfo_scan_if_needed
  • Update docs/CHANGELOG.md unreleased section with the wiring change

Commit step:

git add src/server/fastapi_app.py tests/integration/test_nfo_repair_startup.py docs/
git commit -m "feat: wire NFO repair scan into app startup lifespan"

Task 4: End-to-end validation of NFO repair

Goal: Prove the full flow works with a real (or realistic) deficient NFO file.

Validation steps:

  1. Copy tvshow.nfo.bad into a test series folder inside settings.anime_directory
  2. Ensure tmdb_api_key is configured in data/config.json
  3. Start the server: conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload
  4. Wait for background tasks to complete (check WebSocket or logs)
  5. Open the repaired tvshow.nfo and confirm all previously missing tags are now present and non-empty
  6. Restart the server and confirm the repair scan runs again but does NOT re-queue series whose NFO is already complete (verify via logs: "0 of Y series queued for repair")
  7. Place tvshow.nfo.good in the same folder; restart; confirm it is NOT queued for repair

Run full test suite one final time:

conda run -n AniWorld python -m pytest tests/ -v --tb=short

Final documentation steps:

  • Review docs/NFO_GUIDE.md and add a section "Automatic NFO Repair" explaining the startup scan, which tags are checked, and how to trigger a manual repair via the API (/api/nfo/update/{series})
  • Update docs/CHANGELOG.md to move all unreleased entries under a dated release heading [1.x.x] - 2026-02-22

Final commit step:

git add docs/
git commit -m "docs: document NFO repair feature"