20 KiB
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
- Incremental Development: Implement features incrementally, testing each component thoroughly before moving to the next
- Code Review: Review all generated code for adherence to project standards
- Documentation: Document all public APIs and complex logic
- Testing: Maintain test coverage above 80% for all new code
- Performance: Profile and optimize critical paths, especially download and streaming operations
- Security: Regular security audits and dependency updates
- Monitoring: Implement comprehensive monitoring and alerting
- 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:
- Open
src/core/services/nfo_service.py— read the full file before making any changes - In
_tmdb_to_nfo_model(), add assignments for all fields listed above and pass them toTVShowNFO(...) - Add
_extract_mpaa_rating(content_ratings)helper — mirrors existing_extract_fsk_ratingbut looks for country code"US"; refactor both into_extract_rating_by_country(content_ratings, country_code: str)to eliminate duplication - Add
from datetime import datetimeimport if not already present - No changes needed to
update_tvshow_nfo()— it re-calls_tmdb_to_nfo_model()already - If
TVShowNFOPydantic model is missing fields (originaltitle,outline,sorttitle,dateadded,mpaa,country,watched), add them withOptional[...]defaults ofNone/False - Open
src/core/utils/nfo_generator.py— read the full file before making changes - Confirm
_add_elementcalls exist (ungated) for:tagline,outline,sorttitle,dateadded,originaltitle,year,runtime,premiered,status,country,imdbid - Verify
mpaafallback: preferfskwhennfo_prefer_fsk_rating=TrueANDfsk is not None; otherwise writempaa - Verify
_add_elementdoes NOT suppress"false"strings —watched=Falsemust appear as<watched>false</watched> - Keep both files under 500 lines; if
nfo_service.pyexceeds limit, extract_tmdb_to_nfo_modelintosrc/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.pyand confirm every field has a corresponding_add_elementcall - Delete a real series'
tvshow.nfo, trigger creation, open result and cross-check every tag againsttvshow.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_originaltitletest_tmdb_to_nfo_model_sets_year_from_first_air_datetest_tmdb_to_nfo_model_sets_plot_from_overviewtest_tmdb_to_nfo_model_sets_runtimetest_tmdb_to_nfo_model_sets_premieredtest_tmdb_to_nfo_model_sets_statustest_tmdb_to_nfo_model_sets_imdbidtest_tmdb_to_nfo_model_sets_genrestest_tmdb_to_nfo_model_sets_studios_from_networkstest_tmdb_to_nfo_model_sets_countrytest_tmdb_to_nfo_model_sets_actorstest_tmdb_to_nfo_model_sets_watched_falsetest_tmdb_to_nfo_model_sets_taglinetest_tmdb_to_nfo_model_sets_outline_from_overviewtest_tmdb_to_nfo_model_sets_sorttitle_from_nametest_tmdb_to_nfo_model_sets_dateaddedtest_tmdb_to_nfo_model_sets_mpaa_from_content_ratingstest_extract_rating_by_country_returns_us_ratingtest_extract_rating_by_country_returns_none_when_no_matchtest_extract_rating_by_country_handles_empty_resultstest_generate_nfo_includes_all_required_tags— generate XML from a fully-populatedTVShowNFO; parse withlxml; assert every required tag presenttest_generate_nfo_writes_watched_false— assert<watched>false</watched>not omittedtest_generate_nfo_minimal_model_does_not_crashtest_generate_nfo_writes_fsk_over_mpaa_when_prefer_fsk— both set, prefer_fsk=True → FSK value writtentest_generate_nfo_writes_mpaa_when_no_fsk—fsk=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.mdunder[Unreleased] - Update
docs/ARCHITECTURE.mdunder the NFO services section to reflect new fields and the_extract_rating_by_countryhelper
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,statusimdbid(not justimdb_id)- At least one
genre,studio,country,actor/name watched
Implementation steps:
- Create
src/core/services/nfo_repair_service.py(max 500 lines, type hints, docstrings) - Define
REQUIRED_TAGS: dict[str, str]— XPath expression → human-readable label for each required tag - Implement
def parse_nfo_tags(nfo_path: Path) -> dict— useslxml.etree.parse(), returns tag→value(s), silent on parse errors - Implement
def find_missing_tags(nfo_path: Path) -> list[str]— callsparse_nfo_tags(), returns list of tag names that are absent or have empty text - Implement
def nfo_needs_repair(nfo_path: Path) -> bool— returnsbool(find_missing_tags(nfo_path)) - Implement class
NfoRepairService:__init__(self, nfo_service: NFOService)— inject existingNFOServiceasync def repair_series(self, series_path: Path, series_name: str) -> bool— callsnfo_needs_repair(), logs which tags are missing, callsnfo_service.update_tvshow_nfo(series_path, series_name), returnsTrueif repair was triggered
- 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 giventvshow.nfo.badas input - Manually verify
find_missing_tags()returns an empty list when giventvshow.nfo.goodas 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 copyingtvshow.nfo.bad; assert all 13 expected missing tags are returnedtest_find_missing_tags_with_good_nfo— use fixture copyingtvshow.nfo.good; assert empty list returnedtest_nfo_needs_repair_returns_true_for_bad_nfotest_nfo_needs_repair_returns_false_for_good_nfotest_repair_series_calls_update_when_nfo_needs_repair— mockNFOService.update_tvshow_nfo; assert it is called exactly oncetest_repair_series_skips_when_nfo_is_complete— mockNFOService.update_tvshow_nfo; assert it is NOT calledtest_parse_nfo_tags_handles_missing_file_gracefully— pass nonexistent path; expect empty dict or graceful returntest_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
NfoRepairServiceindocs/ARCHITECTURE.mdunder the NFO services section - Add entry in
docs/CHANGELOG.mdunder 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:
- Open
src/server/services/initialization_service.py - Add import for
NfoRepairServicefromsrc.core.services.nfo_repair_service - 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
NfoRepairServiceinstance usingNFOServiceFactory(follow same pattern as existingperform_nfo_scan_if_needed()) - Iterate all subdirectories in
settings.anime_directory - For each series folder that contains a
tvshow.nfo: callnfo_needs_repair(nfo_path) - If repair needed: queue via
background_loader.add_series_loading_task()(non-blocking), or callrepair_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"
- Guard: if
- 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
awaitwhere needed - Confirm
initialization_service.pystays 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— patchsettings.tmdb_api_key = ""; assert no repair is attemptedtest_perform_nfo_repair_scan_skips_without_anime_directory— patchsettings.anime_directory = ""; assert no repair is attemptedtest_perform_nfo_repair_scan_queues_deficient_series— create temp dir with one bad NFO; mockNfoRepairService.nfo_needs_repairreturning True; assert background_loader is calledtest_perform_nfo_repair_scan_skips_complete_series— create temp dir with one good NFO; mocknfo_needs_repairreturning 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.mdstartup sequence diagram/description to include the newperform_nfo_repair_scanstep - Add note in
docs/CONFIGURATION.mdthattmdb_api_keyis 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:
- Open
src/server/fastapi_app.py - Add
perform_nfo_repair_scanto the existing import fromsrc.server.services.initialization_service - In the
lifespan()async context manager, after the lineawait perform_nfo_scan_if_needed(), add:(pass theawait perform_nfo_repair_scan(background_loader)background_loaderinstance so repairs are queued non-blocking) - Confirm the call is inside the startup block (before
yield), not in the shutdown block
Validation steps:
- Read
fastapi_app.pyand confirm ordering:perform_nfo_scan_if_neededruns beforeperform_nfo_repair_scan - Confirm
background_loaderis 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; mockperform_nfo_repair_scan; assert it was called once during startuptest_startup_repair_scan_receives_background_loader— assertperform_nfo_repair_scanis called with a non-Nonebackground_loaderargument
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.mdto add the new step afterperform_nfo_scan_if_needed - Update
docs/CHANGELOG.mdunreleased 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:
- Copy
tvshow.nfo.badinto a test series folder insidesettings.anime_directory - Ensure
tmdb_api_keyis configured indata/config.json - Start the server:
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload - Wait for background tasks to complete (check WebSocket or logs)
- Open the repaired
tvshow.nfoand confirm all previously missing tags are now present and non-empty - 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") - Place
tvshow.nfo.goodin 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.mdand 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.mdto 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"