chore: update watchlist features and fixes
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
# Anime-Sama Player Fallback System
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Implement automatic player fallback for Anime-Sama downloads to handle cases where the detected player fails
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - `get_download_link_with_fallback()` method in AnimeSamaDownloader
|
||||
> - Player success validation via chunk download test
|
||||
> - Player caching for performance optimization
|
||||
>
|
||||
> **Estimated Effort**: Medium
|
||||
> **Parallel Execution**: NO - sequential implementation
|
||||
> **Critical Path**: Test implementation → AnimeSamaDownloader update → Integration testing
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
User requested a new feature for Anime-Sama provider: ability to change video player on the site. When a player (like Lpayer) fails, the downloader should automatically test different players until finding one that works.
|
||||
|
||||
### Interview Summary
|
||||
**Key Decisions**:
|
||||
- Mode: Automatic - if Lpayer fails, try VidMoly, SendVid, Sibnet, etc. automatically
|
||||
- Success Criterion: Download test (download first 10KB chunk to verify URL works)
|
||||
- Priority Order: VidMoly → SendVid → Sibnet → Lpayer
|
||||
|
||||
**Technical Requirements**:
|
||||
- Test video URL by downloading small chunk (10KB)
|
||||
- If successful, consider player working
|
||||
- Cache working player per anime/series for future episodes
|
||||
- Automatic retry without user intervention
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Implement automatic player fallback in Anime-Sama downloader to handle failed extractions by trying alternative players sequentially.
|
||||
|
||||
### Concrete Deliverables
|
||||
- `AnimeSamaDownloader.get_download_link_with_fallback()` - Main fallback method
|
||||
- `_test_video_url()` - Helper to validate video URL by downloading chunk
|
||||
- Player priority list with caching mechanism
|
||||
- Updated `get_download_link()` to use fallback by default
|
||||
|
||||
### Definition of Done
|
||||
- [ ] Fallback method tries players in priority order
|
||||
- [ ] Video URL validated before returning (10KB download test)
|
||||
- [ ] Working player cached per anime for performance
|
||||
- [ ] All existing Anime-Sama functionality preserved
|
||||
|
||||
### Must Have
|
||||
- Players tested sequentially: VidMoly → SendVid → Sibnet → Lpayer
|
||||
- Success detection via HTTP 200 + valid data download (10KB chunk)
|
||||
- Cache mechanism to avoid re-testing for same anime
|
||||
- Automatic integration with existing download flow
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- NO frontend changes required (backend-only implementation)
|
||||
- NO manual player selection via API (automatic only)
|
||||
- NO changes to other anime sites (Anime-Sama only)
|
||||
- NO breaking changes to existing Anime-Sama functionality
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy (MANDATORY)
|
||||
|
||||
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. No exceptions.
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES
|
||||
- **Automated tests**: Tests-after (unit tests for fallback logic)
|
||||
- **Framework**: pytest
|
||||
|
||||
### QA Policy
|
||||
Every task MUST include agent-executed QA scenarios (see TODO template below).
|
||||
|
||||
- **Unit Tests**: pytest with mocked HTTP clients
|
||||
- **Integration Tests**: Test with real Anime-Sama URLs
|
||||
- **Edge Cases**: All players failing, first player working, cache invalidation
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Sequential Implementation
|
||||
|
||||
Since this is a focused feature on a single file, implementation will be sequential:
|
||||
|
||||
```
|
||||
Step 1: Add URL validation helper (can test independently)
|
||||
→ _test_video_url(url) method
|
||||
|
||||
Step 2: Implement fallback logic
|
||||
→ get_download_link_with_fallback() method
|
||||
|
||||
Step 3: Integrate with existing flow
|
||||
→ Update get_download_link() to use fallback
|
||||
|
||||
Step 4: Add unit tests
|
||||
→ Test fallback logic and URL validation
|
||||
|
||||
Step 5: Integration testing
|
||||
→ Test with real Anime-Sama URLs (Frieren S2 E1)
|
||||
```
|
||||
|
||||
### Dependency Matrix
|
||||
|
||||
- **1**: — 2
|
||||
- **2**: — 3
|
||||
- **3**: — 4, 5
|
||||
- **4**: — 5
|
||||
- **5**: Final
|
||||
|
||||
### Agent Dispatch Summary
|
||||
|
||||
- **1**: `quick` - Helper method
|
||||
- **2**: `quick` - Main fallback logic
|
||||
- **3**: `quick` - Integration
|
||||
- **4**: `quick` - Unit tests
|
||||
- **5**: `quick` - Integration testing
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. Add video URL validation helper method
|
||||
|
||||
**What to do**:
|
||||
- Add `_test_video_url(url: str) -> bool` method to AnimeSamaDownloader
|
||||
- Download first 10KB of video using self.client
|
||||
- Return True if HTTP 200 and valid data received, False otherwise
|
||||
- Include timeout handling (10 seconds for 10KB)
|
||||
- Log validation results for debugging
|
||||
|
||||
**Must NOT do**:
|
||||
- Download entire video
|
||||
- Change existing player extraction logic
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> **Category**: `quick`
|
||||
- Reason: Simple helper method, focused task
|
||||
- **Skills**: None needed
|
||||
- **Skills Evaluated but Omitted**:
|
||||
- No additional skills needed
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO - Sequential
|
||||
- **Parallel Group**: Sequential
|
||||
- **Blocks**: Task 2
|
||||
- **Blocked By**: None
|
||||
|
||||
**References** (CRITICAL):
|
||||
|
||||
> The executor has NO context from your interview. References are their ONLY guide.
|
||||
> Each reference must answer: "What should I look at and WHY?"
|
||||
|
||||
**Pattern References** (existing code to follow):
|
||||
- `app/downloaders/anime_sites/animesama.py:120-150` - Existing video URL extraction methods
|
||||
- `app/downloaders/anime_sites/animesama.py:402-445` - Existing Lplayer extraction pattern
|
||||
|
||||
**API/Type References** (contracts to implement against):
|
||||
- `httpx.AsyncClient.stream()` - For downloading chunks efficiently
|
||||
|
||||
**External References** (libraries and frameworks):
|
||||
- httpx docs: `https://www.python-httpx.org/advanced/#streaming-responses` - Chunked downloads
|
||||
|
||||
**WHY Each Reference Matters**:
|
||||
- Existing extraction methods show how video URLs are currently handled
|
||||
- httpx streaming allows efficient chunk download without loading full video
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
> **AGENT-EXECUTABLE VERIFICATION ONLY** — No human action permitted.
|
||||
> Every criterion MUST be verifiable by running a command or using a tool.
|
||||
|
||||
- [ ] `_test_video_url()` method added to AnimeSamaDownloader
|
||||
- [ ] Downloads first 10KB chunk with 10s timeout
|
||||
- [ ] Returns True if HTTP 200 and data > 0 bytes
|
||||
- [ ] Returns False if timeout, error, or empty response
|
||||
- [ ] Logs validation results (success/failure)
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: Valid video URL returns 200 OK
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Mock a video URL that returns 200 with data
|
||||
Steps:
|
||||
1. python3 -c "from app.downloaders.anime_sites.animesama import AnimeSamaDownloader; d = AnimeSamaDownloader(); result = d._test_video_url('https://example.com/video.mp4'); print(f'Result: {result}')"
|
||||
Expected Result: Returns True
|
||||
Evidence: .sisyphus/evidence/task-1-valid-url.txt
|
||||
|
||||
Scenario: Invalid video URL times out
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Mock a video URL that times out
|
||||
Steps:
|
||||
1. python3 -c "from app.downloaders.anime_sites.animesama import AnimeSamaDownloader; d = AnimeSamaDownloader(); result = d._test_video_url('https://httpbin.org/delay/20'); print(f'Result: {result}')"
|
||||
Expected Result: Returns False (timeout)
|
||||
Evidence: .sisyphus/evidence/task-1-timeout-url.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] Each evidence file named: task-{N}-{scenario-slug}.txt
|
||||
- [ ] Contains test results with True/False output
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(anime-sama): add video URL validation helper method`
|
||||
- Files: `app/downloaders/anime_sites/animesama.py`
|
||||
|
||||
---
|
||||
|
||||
- [x] 2. Implement player fallback logic with priority list
|
||||
|
||||
**What to do**:
|
||||
- Add `get_download_link_with_fallback(url, target_filename=None, anime_page_url=None, episode_title=None)` method
|
||||
- Define player priority list: ['vidmoly', 'sendvid', 'sibnet', 'lpayer']
|
||||
- For each player in priority order:
|
||||
- Try existing extraction methods (_extract_from_vidmoly, etc.)
|
||||
- If extraction succeeds, validate URL with _test_video_url()
|
||||
- If validation succeeds, return (video_url, filename)
|
||||
- Add player caching: `self._working_players = {}` dict to cache working player per anime URL
|
||||
- If cached player exists for anime, try it first
|
||||
- Log each attempted player with success/failure
|
||||
|
||||
**Must NOT do**:
|
||||
- Modify existing _extract_from_* methods
|
||||
- Break existing Anime-Sama download flow
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> **Category**: `quick`
|
||||
- Reason: Sequential logic implementation, clear requirements
|
||||
- **Skills**: None needed
|
||||
- **Skills Evaluated but Omitted**:
|
||||
- No additional skills needed
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO - Depends on Task 1
|
||||
- **Parallel Group**: Sequential
|
||||
- **Blocks**: Task 3
|
||||
- **Blocked By**: Task 1
|
||||
|
||||
**References** (CRITICAL):
|
||||
|
||||
**Pattern References** (existing code to follow):
|
||||
- `app/downloaders/anime_sites/animesama.py:95-170` - VidMoly extraction pattern
|
||||
- `app/downloaders/anime_sites/animesama.py:280-320` - SendVid extraction pattern
|
||||
- `app/downloaders/anime_sites/animesama.py:250-280` - Sibnet extraction pattern
|
||||
- `app/downloaders/anime_sites/animesama.py:402-445` - Lpayer extraction pattern
|
||||
- `app/downloaders/anime_sites/animesama.py:117-120` - Player detection logic
|
||||
|
||||
**WHY Each Reference Matters**:
|
||||
- Existing extraction methods show the interface each player uses
|
||||
- Player detection logic shows how to identify which player URL to extract
|
||||
- Need to understand the signature of each extraction method
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- [ ] `get_download_link_with_fallback()` method added
|
||||
- [ ] Player priority list defined: vidmoly → sendvid → sibnet → lpayer
|
||||
- [ ] Tries each player in order if previous fails
|
||||
- [ ] Validates video URL with _test_video_url() before returning
|
||||
- [ ] Caches working player per anime_page_url
|
||||
- [ ] Logs each player attempt (success/failure)
|
||||
- [ ] Returns (video_url, filename) on first success
|
||||
- [ ] Raises exception if all players fail
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: First player (VidMoly) works
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Mock VidMoly URL that passes validation
|
||||
Steps:
|
||||
1. python3 -c "
|
||||
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader
|
||||
d = AnimeSamaDownloader()
|
||||
# Mock _test_video_url to return True
|
||||
original_test = d._test_video_url
|
||||
d._test_video_url = lambda url: True
|
||||
# Call fallback
|
||||
video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1', episode_title='Episode 1')
|
||||
print(f'Video URL: {video_url[:50] if video_url else None}')
|
||||
print(f'Filename: {filename}')
|
||||
"
|
||||
Expected Result: Returns VidMoly URL, logs "VidMoly player succeeded"
|
||||
Evidence: .sisyphus/evidence/task-2-first-works.txt
|
||||
|
||||
Scenario: First player fails, second works
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Mock VidMoly to fail, SendVid to succeed
|
||||
Steps:
|
||||
1. python3 -c "
|
||||
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader
|
||||
d = AnimeSamaDownloader()
|
||||
call_count = [0]
|
||||
def mock_extract(*args, **kwargs):
|
||||
call_count[0] += 1
|
||||
if call_count[0] == 1: # VidMoly call
|
||||
raise Exception('VidMoly failed')
|
||||
elif call_count[0] == 2: # SendVid call
|
||||
return ('https://sendvid.com/video.mp4', 'sendvid_video.mp4')
|
||||
# Mock extraction methods
|
||||
d._extract_from_vidmoly = lambda *a, **kw: mock_extract(*a, **kw)
|
||||
d._extract_from_sendvid = lambda *a, **kw: mock_extract(*a, **kw)
|
||||
d._test_video_url = lambda url: True
|
||||
# Call fallback
|
||||
video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1')
|
||||
print(f'Video URL: {video_url}')
|
||||
print(f'Used player: {\"SendVid\" if \"sendvid\" in video_url else \"Unknown\"}')
|
||||
"
|
||||
Expected Result: Returns SendVid URL (VidMoly failed, SendVid succeeded)
|
||||
Evidence: .sisyphus/evidence/task-2-second-works.txt
|
||||
|
||||
Scenario: All players fail
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Mock all extractions to fail
|
||||
Steps:
|
||||
1. python3 -c "
|
||||
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader
|
||||
d = AnimeSamaDownloader()
|
||||
def mock_fail(*args, **kwargs):
|
||||
raise Exception('Player failed')
|
||||
# Mock all extraction methods
|
||||
d._extract_from_vidmoly = mock_fail
|
||||
d._extract_from_sendvid = mock_fail
|
||||
d._extract_from_sibnet = mock_fail
|
||||
d._extract_from_lpayer = mock_fail
|
||||
# Call fallback
|
||||
try:
|
||||
video_url, filename = d.get_download_link_with_fallback('test_url|anime_page|Ep1')
|
||||
print('ERROR: Should have raised exception')
|
||||
except Exception as e:
|
||||
print(f'Exception raised: {e}')
|
||||
"
|
||||
Expected Result: Raises exception "All video players failed"
|
||||
Evidence: .sisyphus/evidence/task-2-all-fail.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] Each evidence file contains video URL and logs output
|
||||
- [ ] Test confirms fallback logic works correctly
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(anime-sama): add player fallback logic with priority retry`
|
||||
- Files: `app/downloaders/anime_sites/animesama.py`
|
||||
|
||||
---
|
||||
|
||||
- [x] 3. Integrate fallback into existing get_download_link() method
|
||||
|
||||
**What to do**:
|
||||
- Update `get_download_link()` to use `get_download_link_with_fallback()` by default
|
||||
- Maintain backward compatibility: if direct video URL detected, skip fallback
|
||||
- Pass anime_page_url and episode_title from pipe-separated URL format
|
||||
- Keep existing player detection and direct extraction flow for simple cases
|
||||
|
||||
**Must NOT do**:
|
||||
- Remove existing extraction methods
|
||||
- Change existing player detection logic
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> **Category**: `quick`
|
||||
- Reason: Integration task, minimal changes
|
||||
- **Skills**: None needed
|
||||
- **Skills Evaluated but Omitted**:
|
||||
- No additional skills needed
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO - Depends on Task 2
|
||||
- **Parallel Group**: Sequential
|
||||
- **Blocks**: Task 4, 5
|
||||
- **Blocked By**: Task 2
|
||||
|
||||
**References** (CRITICAL):
|
||||
|
||||
**Pattern References** (existing code to follow):
|
||||
- `app/downloaders/anime_sites/animesama.py:93-120` - Current get_download_link implementation
|
||||
|
||||
**WHY Each Reference Matters**:
|
||||
- Need to understand current logic to integrate fallback without breaking it
|
||||
- Player detection and pipe URL parsing must be preserved
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- [ ] `get_download_link()` calls `get_download_link_with_fallback()` for complex URLs
|
||||
- [ ] Direct video URLs (no pipe format) skip fallback (performance)
|
||||
- [ ] Pipe-separated URLs trigger fallback with anime_page_url and episode_title
|
||||
- [ ] Existing Anime-Sama functionality preserved (VidMoly, SendVid, Sibnet, Lpayer)
|
||||
- [ ] Backward compatible with existing download flow
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: Pipe URL triggers fallback
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Anime-Sama downloader with fallback method
|
||||
Steps:
|
||||
1. python3 -c "
|
||||
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader
|
||||
d = AnimeSamaDownloader()
|
||||
# Mock to test that fallback is called
|
||||
fallback_called = [False]
|
||||
original_fallback = d.get_download_link_with_fallback
|
||||
def mock_fallback(*args, **kwargs):
|
||||
fallback_called[0] = True
|
||||
return original_fallback(*args, **kw)
|
||||
d.get_download_link_with_fallback = mock_fallback
|
||||
# Call with pipe URL
|
||||
d.get_download_link('https://vidmoly.to/vid|https://anime-sama.si/cat/naruto/s1|Episode+1')
|
||||
print(f'Fallback called: {fallback_called[0]}')
|
||||
"
|
||||
Expected Result: Fallback method is called (True)
|
||||
Evidence: .sisyphus/evidence/task-3-pipe-url.txt
|
||||
|
||||
Scenario: Direct video URL skips fallback
|
||||
Tool: Bash (python3)
|
||||
Preconditions: Anime-Sama downloader with fallback method
|
||||
Steps:
|
||||
1. python3 -c "
|
||||
from app.downloaders.anime_sites.animesama import AnimeSamaDownloader
|
||||
d = AnimeSamaDownloader()
|
||||
# Mock to test that fallback is NOT called
|
||||
fallback_called = [False]
|
||||
def mock_fallback(*args, **kwargs):
|
||||
fallback_called[0] = True
|
||||
return ('https://video.mp4', 'video.mp4')
|
||||
d.get_download_link_with_fallback = mock_fallback
|
||||
# Call with direct URL (no pipe)
|
||||
d.get_download_link('https://vidmoly.to/vid')
|
||||
print(f'Fallback called: {fallback_called[0]}')
|
||||
"
|
||||
Expected Result: Fallback method is NOT called (False) - direct extraction used
|
||||
Evidence: .sisyphus/evidence/task-3-direct-url.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] Evidence files show fallback called/not-called correctly
|
||||
- [ ] Integration preserves existing functionality
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(anime-sama): integrate fallback into get_download_link()`
|
||||
- Files: `app/downloaders/anime_sites/animesama.py`
|
||||
|
||||
---
|
||||
|
||||
- [x] 4. Add unit tests for fallback logic
|
||||
|
||||
**What to do**:
|
||||
- Create `tests/test_anime_sama_fallback.py`
|
||||
- Test 1: Fallback tries players in priority order
|
||||
- Test 2: Caching mechanism stores working player
|
||||
- Test 3: All players failing raises exception
|
||||
- Test 4: _test_video_url() returns True/False correctly
|
||||
- Use pytest with mock_httpx_client fixture
|
||||
|
||||
**Must NOT do**:
|
||||
- Make real HTTP requests in tests (use mocks)
|
||||
- Test other anime sites (Anime-Sama only)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> **Category**: `quick`
|
||||
- Reason: Unit tests are straightforward
|
||||
- **Skills**: None needed
|
||||
- **Skills Evaluated but Omitted**:
|
||||
- No additional skills needed
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO - Depends on Task 3
|
||||
- **Parallel Group**: Sequential
|
||||
- **Blocks**: Task 5
|
||||
- **Blocked By**: Task 3
|
||||
|
||||
**References** (CRITICAL):
|
||||
|
||||
**Test References** (testing patterns to follow):
|
||||
- `tests/test_downloaders.py:40-70` - Mock pattern for downloaders
|
||||
- `tests/conftest.py:40-50` - Mock HTTP client fixture
|
||||
|
||||
**WHY Each Reference Matters**:
|
||||
- Mocking patterns show how to simulate HTTP responses without network calls
|
||||
- Conftest fixtures provide reusable test setup
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- [ ] `tests/test_anime_sama_fallback.py` file created
|
||||
- [ ] Test priority order: VidMoly → SendVid → Sibnet → Lpayer
|
||||
- [ ] Test caching: working player reused for same anime
|
||||
- [ ] Test _test_video_url: returns True/False correctly
|
||||
- [ ] Test all players fail: exception raised
|
||||
- [ ] All tests pass with pytest
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: Run all fallback unit tests
|
||||
Tool: Bash
|
||||
Preconditions: Tests implemented in test_anime_sama_fallback.py
|
||||
Steps:
|
||||
1. pytest tests/test_anime_sama_fallback.py -v --tb=short
|
||||
Expected Result: All tests pass
|
||||
Failure Indicators: Any test fails, pytest exit code non-zero
|
||||
Evidence: .sisyphus/evidence/task-4-tests-run.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] pytest output shows all tests passed
|
||||
- [ ] Evidence file contains test summary
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `test(anime-sama): add unit tests for player fallback logic`
|
||||
- Files: `tests/test_anime_sama_fallback.py`
|
||||
|
||||
---
|
||||
|
||||
- [x] 5. Integration testing with real Anime-Sama URLs
|
||||
|
||||
**What to do**:
|
||||
- Test Frieren S2 E1 download with fallback enabled
|
||||
- Verify that fallback tries multiple players if first fails
|
||||
- Check logs to see which player succeeded
|
||||
- Validate that downloaded video is playable
|
||||
- Test with different Anime-Sama URLs to ensure general functionality
|
||||
|
||||
**Must NOT do**:
|
||||
- Only test with Frieren (test variety)
|
||||
- Modify production code during testing
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> **Category**: `quick`
|
||||
- Reason: Integration testing with real data
|
||||
- **Skills**: `playwright` (may be needed for Lpayer)
|
||||
- **Skills Evaluated but Omitted**:
|
||||
- `git-master`: Not needed for testing
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO - Depends on Task 4
|
||||
- **Parallel Group**: Sequential
|
||||
- **Blocks**: Final Verification
|
||||
- **Blocked By**: Task 4
|
||||
|
||||
**References** (CRITICAL):
|
||||
|
||||
**API/Type References** (contracts to implement against):
|
||||
- `/api/anime/download` - Download endpoint
|
||||
- `/api/downloads` - List downloads endpoint
|
||||
|
||||
**WHY Each Reference Matters**:
|
||||
- Need to know how to trigger downloads and check status
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
- [ ] Frieren S2 E1 download completes successfully
|
||||
- [ ] Logs show multiple players tried if first fails
|
||||
- [ ] Downloaded video file is valid (not empty, correct extension)
|
||||
- [ ] Fallback logic works without errors
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: Download Frieren S2 E1 with fallback
|
||||
Tool: Bash (curl) + Playwright
|
||||
Preconditions: Server running, fallback implemented
|
||||
Steps:
|
||||
1. curl -s "http://localhost:3000/api/anime/episodes?url=https://anime-sama.si/catalogue/frieren-s1/vostfr/&lang=vostfr" | python3 -m json.tool
|
||||
2. Extract first episode URL
|
||||
3. curl -X POST "http://localhost:3000/api/anime/download" -H "Content-Type: application/json" -d '{"url": "EPISODE_URL|PAGE_URL|Episode+1"}'
|
||||
4. curl -s "http://localhost:3000/api/downloads" | python3 -m json.tool
|
||||
5. Wait for download to complete (status COMPLETED)
|
||||
6. ls -lh downloads/Frieren*.mp4 2>&1
|
||||
Expected Result: Download completes with status COMPLETED, video file exists with > 1MB
|
||||
Failure Indicators: Status FAILED, no video file, file size < 1MB
|
||||
Evidence: .sisyphus/evidence/task-5-frieren-download.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] Evidence file contains download status
|
||||
- [ ] Video file exists and is playable
|
||||
|
||||
**Commit**: YES (if successful)
|
||||
- Message: `test(anime-sama): verify fallback works with Frieren S2 E1`
|
||||
- Files: `downloads/` (test artifacts)
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [ ] F1. **Unit Test Coverage** — `pytest`
|
||||
Run pytest on anime-sama tests to ensure fallback logic is covered.
|
||||
- Run: `pytest tests/test_anime_sama_fallback.py -v --cov=app.downloaders.anime_sites.animesama`
|
||||
- Verify: All tests pass, coverage > 80% for new methods
|
||||
Output: `Tests [N/N pass] | Coverage [%] | VERDICT`
|
||||
|
||||
- [ ] F2. **Real Download Test** — `curl` + `Bash`
|
||||
Test actual download with Anime-Sama fallback enabled.
|
||||
- Trigger: Download Frieren S2 E1 via API
|
||||
- Verify: Download completes, fallback logs visible, file valid
|
||||
Output: `Download [COMPLETE/FAILED] | Player [name] | File [size] | VERDICT`
|
||||
|
||||
- [ ] F3. **Log Analysis** — `Bash`
|
||||
Check server logs for fallback behavior.
|
||||
- Run: `tail -100 /tmp/uvicorn.log | grep -E "(LPAYER|fallback|player)"`
|
||||
- Verify: Multiple player attempts logged when first fails
|
||||
Output: `Attempts [N] | Success [True/False] | VERDICT`
|
||||
|
||||
- [ ] F4. **No Regressions** — `pytest`
|
||||
Ensure existing Anime-Sama functionality still works.
|
||||
- Run: `pytest tests/test_anime_sama.py -v -k "not fallback"`
|
||||
- Verify: All existing tests pass
|
||||
Output: `Tests [N/N pass] | VERDICT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **1**: `feat(anime-sama): add video URL validation helper method` — `app/downloaders/anime_sites/animesama.py`
|
||||
- **2**: `feat(anime-sama): add player fallback logic with priority retry` — `app/downloaders/anime_sites/animesama.py`
|
||||
- **3**: `feat(anime-sama): integrate fallback into get_download_link()` — `app/downloaders/anime_sites/animesama.py`
|
||||
- **4**: `test(anime-sama): add unit tests for player fallback logic` — `tests/test_anime_sama_fallback.py`
|
||||
- **5**: `test(anime-sama): verify fallback works with real downloads` — `downloads/` (test artifacts)
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
# Unit tests
|
||||
pytest tests/test_anime_sama_fallback.py -v
|
||||
|
||||
# Integration test
|
||||
curl -X POST "http://localhost:3000/api/anime/download" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url": "URL|PAGE|TITLE"}'
|
||||
|
||||
# Check logs
|
||||
tail -50 /tmp/uvicorn.log | grep fallback
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [ ] Fallback method tries players in priority order
|
||||
- [ ] Video URLs validated before returning (10KB download test)
|
||||
- [ ] Working player cached per anime for performance
|
||||
- [ ] All unit tests pass
|
||||
- [ ] Real download test succeeds
|
||||
- [ ] No regressions in existing Anime-Sama functionality
|
||||
- [ ] All "Must Have" present
|
||||
- [ ] All "Must NOT Have" absent
|
||||
@@ -0,0 +1,46 @@
|
||||
# Plan: Faire fonctionner Frieren S2 - Analyse et Solutions
|
||||
|
||||
## Analyse de la situation
|
||||
|
||||
### Fournisseurs disponibles pour Frieren S2
|
||||
|
||||
| Episode | Fournisseur | Status |
|
||||
|---------|-------------|--------|
|
||||
| 1 | Lpayer | ❌ Besoin JavaScript |
|
||||
| 2 | VidMoly | ❌ Bloqué (ffmpeg) |
|
||||
| 3 | Sibnet | ❌ 403 Forbidden |
|
||||
| 4 | SendVid + VidMoly | ❌ Bloqué |
|
||||
| 5 | Dingtez | ❌ JavaScript obfusqué |
|
||||
|
||||
### Causes du blocage
|
||||
|
||||
1. **Lpayer** : Charge les vidéos avec JavaScript React - Playwright n'arrive pas à extraire
|
||||
2. **VidMoly** : Vérifie si ffmpeg est disponible, bloque les requêtes automatisées
|
||||
3. **Sibnet** : Retourne 403 Forbidden pour les requêtes non-browser
|
||||
4. **SendVid** : Bloque les requêtes automatisées
|
||||
5. **Dingtez** : JavaScript obfusqué avec JWPlayer
|
||||
|
||||
---
|
||||
|
||||
## Solutions possibles
|
||||
|
||||
### Solution 1: Interface de saisie manuelle (PRIORITÉ)
|
||||
- [ ] Ajouter un champ "URL vidéo directe" dans l'interface
|
||||
- [ ] L'utilisateur colle l'URL qu'il a trouvée ailleurs
|
||||
- [ ] Le système télécharge directement sans extraction
|
||||
|
||||
### Solution 2: Real-Debrid
|
||||
- [ ] Intégrer l'API Real-Debrid
|
||||
- [ ] Le service débride les URLs automatiquement
|
||||
- [ ] Fonctionne avec tous les hébergeurs
|
||||
|
||||
### Solution 3: Navigateur Playwright intégré
|
||||
- [ ] Utiliser Playwright pour TOUTES les extractions
|
||||
- [ ] Plus lent mais plus fiable
|
||||
- [ ] Nécessite plus de ressources
|
||||
|
||||
---
|
||||
|
||||
## Recommandation
|
||||
|
||||
Commencer par **Solution 1** (la plus simple et fiable) puis **Solution 2** (Real-Debrid).
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,423 @@
|
||||
# Harmonize Watchlist Design - Align with Main Page
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Harmonize the visual design of watchlist page to match /web page while keeping watchlist as separate autonomous page.
|
||||
|
||||
> **Deliverables**:
|
||||
> - Update watchlist.html to use same background gradient and styling as /web
|
||||
> - Unify header design (colors, layout, icons)
|
||||
> - Align button styles to match /web patterns
|
||||
> - Maintain watchlist functionality (no breaking changes)
|
||||
|
||||
> **Estimated Effort**: Medium
|
||||
> **Parallel Execution**: NO - single task
|
||||
> **Critical Path**: CSS updates → styling verification → commit
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request Summary
|
||||
User identified that watchlist (/watchlist) page has a completely different design from the main page (/web), creating UX inconsistency:
|
||||
- Watchlist has dark violet gradient background, /web has cleaner light gradient
|
||||
- Watchlist has custom header "📋 Ma Watchlist", /web has unified navigation tabs
|
||||
- Watchlist has its own navigation button, /web has tab-based navigation
|
||||
- Different color schemes, layouts, and styling patterns
|
||||
|
||||
### User's Decision
|
||||
User chose **Option 2: Harmonize watchlist design** - adapt watchlist visual design to match /web styling while keeping it as a separate page.
|
||||
|
||||
### Key Findings
|
||||
- /web uses light gradient background (135deg, #1e1e2e 0%, #2d1b69 0%, #1e1e2e 100%)
|
||||
- Watchlist currently uses dark violet gradient background
|
||||
- /web has tab-based navigation (Accueil, Anime, Série, Fournisseurs, Watchlist)
|
||||
- Watchlist has standalone page design
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Harmonize the visual design of templates/watchlist.html to match the styling and patterns of templates/index.html (/web), creating visual consistency across the application.
|
||||
|
||||
### Concrete Deliverables
|
||||
- Updated `templates/watchlist.html` background to match /web
|
||||
- Unified header design colors and layout
|
||||
- Aligned button styles (btn-primary, btn-secondary)
|
||||
- Consistent typography and spacing
|
||||
- Maintained all watchlist functionality (scheduler, stats, search, add/remove items)
|
||||
|
||||
### Definition of Done
|
||||
- [ ] Watchlist background gradient matches /web
|
||||
- [ ] Header text color and styling matches /web
|
||||
- [ ] Button styles (btn-primary, btn-secondary) match /web
|
||||
- [ ] Overall visual appearance is consistent with /web
|
||||
- [ ] All watchlist features still work (no breaking changes)
|
||||
|
||||
### Must Have
|
||||
- Harmonize visual design with /web
|
||||
- Match background gradient colors
|
||||
- Align header styling (fonts, colors, icons)
|
||||
- Unify button class styles
|
||||
- Maintain all existing watchlist functionality
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- **DO NOT remove watchlist functionality** - scheduler, stats, notifications must still work
|
||||
- **DO NOT change /web design** - only adapt watchlist to match
|
||||
- **DO NOT break existing URL routes** - /watchlist and /web must both work
|
||||
- **DO NOT modify JavaScript files** - only HTML/CSS changes
|
||||
- **DO NOT add new features** - this is visual harmonization only
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES (uvicorn server)
|
||||
- **Automated tests**: NO (visual changes, manual QA)
|
||||
- **Framework**: None - manual browser verification
|
||||
- **Rationale**: This is visual CSS/template change, requires manual browser verification
|
||||
|
||||
### QA Policy
|
||||
Visual verification required for design changes:
|
||||
- Use dev-browser (playwright) to load both pages
|
||||
- Compare visual appearance side-by-side
|
||||
- Verify no functionality broken
|
||||
- Evidence saved to `.sisyphus/evidence/`
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Sequential Execution
|
||||
|
||||
```
|
||||
Task 1: Update Watchlist Background Gradient
|
||||
- Modify templates/watchlist.html
|
||||
- Replace dark violet gradient with /web's light gradient
|
||||
- Verify page loads and looks correct
|
||||
|
||||
Task 2: Harmonize Header Design
|
||||
- Update header colors, fonts, layout
|
||||
- Match /web navigation header styling
|
||||
- Ensure text colors are consistent
|
||||
- [ ] 2. Harmonize Header Design
|
||||
Task 3: Align Button Styles
|
||||
- Update button classes to use same styles as /web
|
||||
- Verify hover states and interactions
|
||||
- Ensure responsive behavior matches
|
||||
|
||||
- [ ] 4. Final Verification
|
||||
- Load both /web and /watchlist in browser
|
||||
- Take screenshots for comparison
|
||||
- Verify all functionality works
|
||||
```
|
||||
|
||||
### Agent Dispatch Summary
|
||||
|
||||
- **1**: **1** — T1 (visual-engineering)
|
||||
- **2**: **1** — T2 (visual-engineering)
|
||||
- **3**: **1** — T3 (visual-engineering)
|
||||
- **4**: **1** — T4 (visual-engineering)
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] 1. Update Watchlist Background Gradient
|
||||
|
||||
**What to do**:
|
||||
- Read `templates/watchlist.html` to find current background styling
|
||||
- Read `templates/index.html` to get the light gradient background
|
||||
- Replace watchlist's dark violet gradient: `background: linear-gradient(135deg, #1e1e2e 0%, #2d1b69 0%, #1e1e2e 100%)`
|
||||
- With /web's light gradient: Need to check index.html for exact colors
|
||||
|
||||
**Must NOT do**:
|
||||
- Remove watchlist functionality (scheduler, stats, search)
|
||||
- Change the structure of the page
|
||||
- Modify JavaScript files
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- Reason: CSS styling update for visual consistency
|
||||
- **Skills**: []
|
||||
- No special skills needed - CSS gradient change
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Single task
|
||||
- **Blocks**: Task 2
|
||||
- **Blocked By**: None (can start immediately)
|
||||
|
||||
**References**:
|
||||
|
||||
**Pattern References**:
|
||||
- `templates/index.html` - Reference for correct background gradient
|
||||
- `templates/watchlist.html` - File to modify
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
```bash
|
||||
# Watchlist uses light gradient like /web
|
||||
grep -c "linear-gradient(135deg" templates/watchlist.html
|
||||
Expected: 1
|
||||
```
|
||||
|
||||
**QA Scenarios (MANDATORY — task is INCOMPLETE without these):**
|
||||
|
||||
```
|
||||
Scenario: Watchlist page uses light gradient background
|
||||
Tool: dev-browser (playwright)
|
||||
Preconditions: Server running on port 3000
|
||||
Steps:
|
||||
1. Navigate to http://localhost:3000/watchlist
|
||||
2. Wait for page to load (timeout 10s)
|
||||
3. Take screenshot of page background
|
||||
4. Navigate to http://localhost:3000/web
|
||||
5. Take screenshot for comparison
|
||||
Expected Result: Watchlist background matches /web's light gradient
|
||||
Failure Indicators: Background still dark violet, colors don't match
|
||||
Evidence: .sisyphus/evidence/task-1-background-gradient.png
|
||||
```
|
||||
|
||||
**Commit**: NO
|
||||
- Groups with Task 2, 3
|
||||
|
||||
- [ ] 2. Harmonize Header Design
|
||||
|
||||
**What to do**:
|
||||
- Read `templates/watchlist.html` to check current header styling
|
||||
- Read `templates/index.html` to get header reference
|
||||
- Update watchlist header colors to match /web's color scheme
|
||||
- Update fonts to match /web typography
|
||||
- Ensure header layout and spacing match /web
|
||||
- Keep "📋 Ma Watchlist" title but update colors
|
||||
|
||||
**Must NOT do**:
|
||||
- Remove header functionality
|
||||
- Change header text/title
|
||||
- Remove the "Retour à l'accueil" button added earlier
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- Reason: Header styling harmonization
|
||||
- **Skills**: []
|
||||
- CSS styling task
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Single task
|
||||
- **Blocks**: Task 3
|
||||
- **Blocked By**: Task 1 (background must be updated first)
|
||||
|
||||
**References**:
|
||||
|
||||
**Pattern References**:
|
||||
- `templates/index.html` - Reference for header styling
|
||||
- `templates/watchlist.html` - File to modify
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
```bash
|
||||
# Header uses same colors as /web
|
||||
# Verify no dark violet colors remain
|
||||
```
|
||||
|
||||
**QA Scenarios (MANDATORY):**
|
||||
|
||||
```
|
||||
Scenario: Header design matches /web
|
||||
Tool: dev-browser (playwright)
|
||||
Preconditions: Tasks 1 complete, server running
|
||||
Steps:
|
||||
1. Navigate to http://localhost:3000/watchlist
|
||||
2. Take screenshot of header section
|
||||
3. Navigate to http://localhost:3000/web
|
||||
4. Take screenshot of navigation header
|
||||
5. Compare screenshots side-by-side
|
||||
Expected Result: Watchlist header colors, fonts, layout match /web
|
||||
Failure Indicators: Different colors, fonts mismatched, layout differences
|
||||
Evidence: .sisyphus/evidence/task-2-header-harmonization.png
|
||||
```
|
||||
|
||||
**Commit**: NO
|
||||
- Groups with Task 3
|
||||
|
||||
- [ ] 3. Align Button Styles
|
||||
|
||||
**What to do**:
|
||||
- Read `templates/watchlist.html` to identify all button elements
|
||||
- Read `templates/index.html` to get button class references
|
||||
- Ensure all buttons use consistent classes (btn-primary, btn-secondary)
|
||||
- Verify hover states and interactions work correctly
|
||||
- Make sure "Retour à l'accueil" button style is aligned
|
||||
|
||||
**Must NOT do**:
|
||||
- Change button functionality or behavior
|
||||
- Remove any buttons
|
||||
- Modify JavaScript event handlers
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- Reason: Button styling alignment
|
||||
- **Skills**: []
|
||||
- CSS class updates
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Single task
|
||||
- **Blocks**: Task 4
|
||||
- **Blocked By**: Task 2 (header must be updated first)
|
||||
|
||||
**References**:
|
||||
|
||||
**Pattern References**:
|
||||
- `templates/index.html` - Reference for button styles
|
||||
- `templates/watchlist.html` - File to modify
|
||||
- `static/css/style.css` - Button class definitions (if exists)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
```bash
|
||||
# All buttons use btn-primary or btn-secondary classes
|
||||
grep -o "class=\"btn-" templates/watchlist.html | sort | uniq
|
||||
Expected: btn-primary, btn-secondary (or similar consistent classes)
|
||||
```
|
||||
|
||||
**QA Scenarios (MANDATORY):**
|
||||
|
||||
```
|
||||
Scenario: Button styles are consistent with /web
|
||||
Tool: dev-browser (playwright)
|
||||
Preconditions: Tasks 1, 2 complete
|
||||
Steps:
|
||||
1. Navigate to http://localhost:3000/watchlist
|
||||
- [ ] 3. Align Button Styles
|
||||
3. Click buttons, verify interactions work
|
||||
4. Check no console errors
|
||||
Expected Result: All buttons have consistent styling with /web, hover states work
|
||||
Failure Indicators: Different button styles, broken interactions, console errors
|
||||
Evidence: .sisyphus/evidence/task-3-button-alignment.png
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `style(ui): Harmonize watchlist design to match /web`
|
||||
- Files: `templates/watchlist.html`
|
||||
|
||||
- [ ] 4. Final Verification
|
||||
|
||||
**What to do**:
|
||||
- Start server if not running: `uvicorn main:app --host 0.0.0.0 --port 3000`
|
||||
- Navigate to `/web` and verify page works
|
||||
- Navigate to `/watchlist` and verify page works
|
||||
- Take comparison screenshots
|
||||
- Verify navigation works both ways
|
||||
- Check browser console for errors
|
||||
- Verify watchlist features (search, scheduler, stats, add/remove items) still work
|
||||
|
||||
**Must NOT do**:
|
||||
- Make any code changes
|
||||
- Modify functionality
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `unspecified-high`
|
||||
- Reason: Final integration verification
|
||||
- **Skills**: [`dev-browser`]
|
||||
- dev-browser: Use Playwright for browser automation and screenshots
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Final task
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: Tasks 1, 2, 3 (all tasks must complete)
|
||||
|
||||
**References**:
|
||||
|
||||
**Pattern References**:
|
||||
- `templates/index.html` - Reference for expected design
|
||||
- `templates/watchlist.html` - File being modified
|
||||
|
||||
**Acceptance Criteria**:
|
||||
|
||||
```bash
|
||||
# Both pages work
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/web
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/watchlist
|
||||
Expected: 200 for both
|
||||
```
|
||||
|
||||
**QA Scenarios (MANDATORY):**
|
||||
|
||||
```
|
||||
Scenario: Visual design is harmonized between /web and /watchlist
|
||||
Tool: dev-browser (playwright)
|
||||
Preconditions: All styling tasks complete, server running
|
||||
Steps:
|
||||
1. Navigate to http://localhost:3000/web
|
||||
2. Take full page screenshot
|
||||
3. Navigate to http://localhost:3000/watchlist
|
||||
4. Take full page screenshot
|
||||
5. Compare side-by-side
|
||||
6. Verify backgrounds match
|
||||
7. Verify header styles match
|
||||
8. Verify button styles match
|
||||
Expected Result: Visual design is consistent between both pages
|
||||
Failure Indicators: Color mismatch, style differences, broken features
|
||||
Evidence: .sisyphus/evidence/task-4-verification-screenshot.png
|
||||
```
|
||||
|
||||
**Commit**: NO
|
||||
- This is verification only, no code changes
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave (MANDATORY — after ALL implementation tasks)
|
||||
|
||||
> 3 review agents run in PARALLEL. ALL must APPROVE. Rejection → fix → re-run.
|
||||
|
||||
- [ ] F1. **Visual Design Review** — `visual-engineering`
|
||||
Compare watchlist and /web designs side-by-side. Verify colors, gradients, typography, spacing, and layout are harmonized. Check for any visual inconsistencies.
|
||||
|
||||
Output: `Background [MATCH/MISMATCH] | Header [MATCH/MISMATCH] | Buttons [MATCH/MISMATCH] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [ ] F2. **Functionality Verification** — `unspecified-high` (+ `dev-browser` skill)
|
||||
Navigate to /watchlist and verify all features work: search, scheduler controls, stats display, add/remove items, navigation. Check browser console for errors.
|
||||
|
||||
Output: `Features [N/N working] | Console Errors [0/N] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [ ] F3. **Code Quality Check** — `quick`
|
||||
Check for CSS syntax errors, invalid colors, or broken HTML structure.
|
||||
|
||||
Output: `CSS [VALID/INVALID] | HTML [VALID/INVALID] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **1**: `style(ui): Harmonize watchlist design to match /web`
|
||||
- Files: `templates/watchlist.html`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
# Watchlist uses light gradient
|
||||
grep -c "linear-gradient(135deg" templates/watchlist.html
|
||||
# Expected: 1
|
||||
|
||||
# Both pages work
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/web
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/watchlist
|
||||
# Expected: 200 for both
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [ ] Watchlist background matches /web
|
||||
- [ ] Header design harmonized with /web
|
||||
- [ ] Button styles aligned with /web
|
||||
- [ ] All watchlist features still work
|
||||
- [ ] Both pages load without errors
|
||||
- [ ] Visual design is consistent
|
||||
@@ -0,0 +1,535 @@
|
||||
# Plan : Refonte du Système Watchlist
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Objectif** : Refaire le système de watchlist avec auto-téléchargement, notifications et stockage SQLite
|
||||
>
|
||||
> **Deliverables** :
|
||||
> - Base de données SQLite pour la watchlist
|
||||
> - API REST pour gérer les animes suivis
|
||||
> - Système d'auto-téléchargement (vérification automatique des nouveaux épisodes)
|
||||
> - Système de notifications (in-app)
|
||||
> - Interface frontend (page séparée, même style que le reste)
|
||||
>
|
||||
> **Effort** : XL
|
||||
> **Exécution** : En waves parallèles
|
||||
|
||||
---
|
||||
|
||||
## Contexte
|
||||
|
||||
### Système Actuel
|
||||
- Stockage JSON (`config/watchlist.json`)
|
||||
- Pas de SQLite
|
||||
- Auto-download basique via scheduler
|
||||
- Pas de système de notifications
|
||||
- Interface intégrée à la page principale
|
||||
|
||||
### Besoins Utilisateur
|
||||
- Auto-téléchargement des nouveaux épisodes ✅
|
||||
- Notifications quand un nouvel épisode est dispo ✅
|
||||
- Stockage SQLite ✅
|
||||
- Même style que le reste du site ✅
|
||||
- Page séparée ✅
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Objectif Principal
|
||||
Créer un système de watchlist complet permettant de :
|
||||
1. Suivre des animes (ajout via recherche)
|
||||
2. Détecter automatiquement les nouveaux épisodes
|
||||
3. Télécharger automatiquement les nouveaux épisodes
|
||||
4. Notifier l'utilisateur quand un nouvel épisode est disponible
|
||||
|
||||
### Deliverables Concrets
|
||||
- [ ] Base de données SQLite (`config/watchlist.db`)
|
||||
- [ ] Modèles Pydantic pour la watchlist
|
||||
- [ ] API endpoints (CRUD + actions)
|
||||
- [ ] Service d'auto-check (scheduler)
|
||||
- [ ] Service de notifications
|
||||
- [ ] Page frontend dédiée
|
||||
- [ ] Intégration avec le système de download existant
|
||||
|
||||
### Définition de Terminé
|
||||
- [ ] Un anime peut être ajouté à la watchlist
|
||||
- [ ] La watchlist affiche tous les animes suivis
|
||||
- [ ] Les épisodes peuvent être téléchargés manuellement
|
||||
- [ ] Le scheduler vérifie automatiquement les nouveaux épisodes
|
||||
- [ ] Les nouveaux épisodes sont téléchargés automatiquement
|
||||
- [ ] Une notification apparaît quand un nouvel épisode est dispo
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Structure des Fichiers
|
||||
|
||||
```
|
||||
app/
|
||||
├── watchlist/
|
||||
│ ├── __init__.py
|
||||
│ ├── models.py # Modèles Pydantic
|
||||
│ ├── database.py # Connexion SQLite
|
||||
│ ├── service.py # Logique métier
|
||||
│ ├── scheduler.py # Auto-check
|
||||
│ └── notifications.py # Notifications
|
||||
├── routes/
|
||||
│ └── watchlist.py # API endpoints
|
||||
static/
|
||||
└── js/
|
||||
└── watchlist/ # Frontend
|
||||
├── index.js
|
||||
├── components/
|
||||
└── style.css
|
||||
```
|
||||
|
||||
### Schéma Base de Données (SQLite)
|
||||
|
||||
```sql
|
||||
-- Table principale : watchlist items
|
||||
CREATE TABLE watchlist_items (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
anime_title TEXT NOT NULL,
|
||||
anime_url TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
lang TEXT DEFAULT 'vostfr',
|
||||
poster_image TEXT,
|
||||
cover_image TEXT,
|
||||
synopsis TEXT,
|
||||
genres TEXT, -- JSON array
|
||||
|
||||
-- Tracking
|
||||
status TEXT DEFAULT 'active', -- active, paused, completed
|
||||
auto_download INTEGER DEFAULT 1,
|
||||
quality_preference TEXT DEFAULT 'auto',
|
||||
last_episode_downloaded INTEGER DEFAULT 0,
|
||||
total_episodes INTEGER,
|
||||
last_checked_at TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Table : Episodes téléchargés
|
||||
CREATE TABLE downloaded_episodes (
|
||||
id TEXT PRIMARY KEY,
|
||||
watchlist_item_id TEXT NOT NULL,
|
||||
episode_number INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
file_path TEXT,
|
||||
file_size INTEGER,
|
||||
downloaded_at TEXT NOT NULL,
|
||||
FOREIGN KEY (watchlist_item_id) REFERENCES watchlist_items(id)
|
||||
);
|
||||
|
||||
-- Table : Notifications
|
||||
CREATE TABLE notifications (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
watchlist_item_id TEXT,
|
||||
type TEXT NOT NULL, -- new_episode, download_complete, error
|
||||
title TEXT NOT NULL,
|
||||
message TEXT,
|
||||
read INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (watchlist_item_id) REFERENCES watchlist_items(id)
|
||||
);
|
||||
|
||||
-- Table : Settings
|
||||
CREATE TABLE watchlist_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
check_interval_hours INTEGER DEFAULT 6,
|
||||
auto_download_enabled INTEGER DEFAULT 1,
|
||||
max_concurrent_downloads INTEGER DEFAULT 2,
|
||||
notifications_enabled INTEGER DEFAULT 1
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Wave 1 (Fondations)
|
||||
```
|
||||
Tâches :
|
||||
├── 1. Créer structure du module watchlist/
|
||||
├── 2. Créer database.py (connexion SQLite, migrations)
|
||||
├── 3. Créer models.py (Pydantic models)
|
||||
├── 4. Créer service.py (CRUD operations)
|
||||
└── 5. Mettre à jour models/__init__.py
|
||||
|
||||
Dépendances :Aucune (start immediate)
|
||||
```
|
||||
|
||||
### Wave 2 (API + Scheduler)
|
||||
```
|
||||
Tâches (dépendent de Wave 1) :
|
||||
├── 6. Créer routes/watchlist.py (API endpoints)
|
||||
├── 7. Créer scheduler.py (auto-check)
|
||||
├── 8. Intégrer scheduler dans main.py
|
||||
└── 9. Créer notifications.py
|
||||
|
||||
Bloqué par : 1-5
|
||||
```
|
||||
|
||||
### Wave 3 (Frontend)
|
||||
```
|
||||
Tâches (dépendent de Wave 2) :
|
||||
├── 10. Créer page HTML watchlist.html
|
||||
├── 11. Créer watchlist-ui.js (logique)
|
||||
├── 12. Ajouter CSS pour la page
|
||||
└── 13. Ajouter routes pour servir la page
|
||||
|
||||
Bloqué par : 6-9
|
||||
```
|
||||
|
||||
### Wave 4 (Intégration + Tests)
|
||||
```
|
||||
Tâches :
|
||||
├── 14. Tester l'ajout d'un anime
|
||||
├── 15. Tester le téléchargement manuel
|
||||
├── 16. Tester l'auto-download
|
||||
├── 17. Tester les notifications
|
||||
└── 18. Nettoyer l'ancien code
|
||||
|
||||
Bloqué par : 10-13
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] 1. **Créer la structure du module watchlist/**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer le répertoire `app/watchlist/`
|
||||
- Créer `__init__.py` avec exports
|
||||
|
||||
**Pas faire** :
|
||||
- Toucher aux autres modules
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: Le répertoire existe
|
||||
Tool: Bash
|
||||
Command: ls -la app/watchlist/
|
||||
Expected: Le répertoire existe avec __init__.py
|
||||
```
|
||||
|
||||
- [ ] 2. **Créer database.py (connexion SQLite)**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/watchlist/database.py`
|
||||
- Implémenter connexion SQLite avec `sqlite3`
|
||||
- Implémenter fonctions : `init_db()`, `get_connection()`, `migrate()`
|
||||
- Créer les tables définies dans le schéma
|
||||
|
||||
**Pas faire** :
|
||||
- Toucher aux autres fichiers
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: La base de données est créée
|
||||
Tool: Bash
|
||||
Command: python3 -c "from app.watchlist.database import init_db; init_db(); import os; print(os.path.exists('config/watchlist.db'))"
|
||||
Expected: True
|
||||
|
||||
Scenario: Les tables existent
|
||||
Tool: Bash
|
||||
Command: sqlite3 config/watchlist.db ".tables"
|
||||
Expected: watchlist_items downloaded_episodes notifications watchlist_settings
|
||||
```
|
||||
|
||||
- [ ] 3. **Créer models.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/watchlist/models.py`
|
||||
- Définir les modèles Pydantic :
|
||||
- WatchlistItem, WatchlistItemCreate, WatchlistItemUpdate
|
||||
- DownloadedEpisode
|
||||
- Notification, NotificationCreate
|
||||
- WatchlistSettings
|
||||
- Utiliser les types existants de `app/models/`
|
||||
|
||||
**Pas faire** :
|
||||
- Dupliquer les types existants
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: Les modèles peuvent être importés
|
||||
Tool: Bash
|
||||
Command: python3 -c "from app.watchlist.models import WatchlistItem, Notification; print('OK')"
|
||||
Expected: OK (no error)
|
||||
```
|
||||
|
||||
- [ ] 4. **Créer service.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/watchlist/service.py`
|
||||
- Implémenter `WatchlistService` avec :
|
||||
- `add_item()`, `get_items()`, `get_item()`, `update_item()`, `delete_item()`
|
||||
- `mark_episode_downloaded()`, `get_downloaded_episodes()`
|
||||
- `create_notification()`, `get_notifications()`, `mark_notification_read()`
|
||||
- `get_settings()`, `update_settings()`
|
||||
- `get_items_due_for_check()`
|
||||
- Utiliser SQLite directement (pas d'ORM)
|
||||
|
||||
**Pas faire** :
|
||||
- Toucher au frontend
|
||||
|
||||
**Agent recommandé** : `unspecified-high`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: Ajouter un item à la watchlist
|
||||
Tool: Bash
|
||||
Command: python3 -c "
|
||||
from app.watchlist.service import WatchlistService
|
||||
svc = WatchlistService()
|
||||
item = svc.add_item(user_id='test', anime_title='Test Anime', anime_url='https://example.com', provider_id='anime-sama')
|
||||
print(f'Created: {item.id}')
|
||||
"
|
||||
Expected: Un UUID est retourné
|
||||
|
||||
Scenario: Récupérer les items
|
||||
Tool: Bash
|
||||
Command: python3 -c "
|
||||
from app.watchlist.service import WatchlistService
|
||||
svc = WatchlistService()
|
||||
items = svc.get_items()
|
||||
print(f'Count: {len(items)}')
|
||||
"
|
||||
Expected: Count: 1
|
||||
```
|
||||
|
||||
- [ ] 5. **Mettre à jour models/__init__.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Ajouter export des nouveaux modèles si besoin
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
- [ ] 6. **Créer routes/watchlist.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/routes/watchlist.py`
|
||||
- Définir les endpoints :
|
||||
- `GET /api/watchlist` - Liste des items
|
||||
- `POST /api/watchlist` - Ajouter un item
|
||||
- `GET /api/watchlist/{id}` - Détail d'un item
|
||||
- `PUT /api/watchlist/{id}` - Modifier un item
|
||||
- `DELETE /api/watchlist/{id}` - Supprimer un item
|
||||
- `POST /api/watchlist/{id}/download/{episode}` - Télécharger un épisode
|
||||
- `GET /api/watchlist/{id}/episodes` - Épisodes téléchargés
|
||||
- `GET /api/watchlist/notifications` - Liste des notifications
|
||||
- `PUT /api/watchlist/notifications/{id}/read` - Marquer comme lu
|
||||
- `GET /api/watchlist/settings` - Settings
|
||||
- `PUT /api/watchlist/settings` - Mettre à jour settings
|
||||
- Ajouter auth (Bearer token)
|
||||
- Intégrer avec `download_manager` pour les téléchargements
|
||||
|
||||
**Agent recommandé** : `unspecified-high`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: L'API répond
|
||||
Tool: Bash
|
||||
Command: curl -s http://127.0.0.1:3000/api/watchlist
|
||||
Expected: {"items": [...], "count": N}
|
||||
```
|
||||
|
||||
- [ ] 7. **Créer scheduler.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/watchlist/scheduler.py`
|
||||
- Implémenter `WatchlistScheduler` :
|
||||
- `start()`, `stop()`
|
||||
- `_check_loop()` - Boucle principale
|
||||
- `check_item(item)` - Vérifier un anime
|
||||
- `download_new_episodes(item, new_episodes)` - Télécharger
|
||||
- Utiliser `APScheduler` (déjà dans requirements)
|
||||
- Intervalle configurable (défaut: 6h)
|
||||
|
||||
**Agent recommandé** : `unspecified-high`
|
||||
|
||||
- [ ] 8. **Intégrer scheduler dans main.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Importer et initialiser le scheduler
|
||||
- Ajouter au startup event
|
||||
- Ajouter au shutdown event
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
- [ ] 9. **Créer notifications.py**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `app/watchlist/notifications.py`
|
||||
- Implémenter `NotificationService`
|
||||
- Types de notifications :
|
||||
- `new_episode` - Nouvel épisode détecté
|
||||
- `download_started` - Téléchargement commencé
|
||||
- `download_complete` - Téléchargement terminé
|
||||
- `download_error` - Erreur de téléchargement
|
||||
- Stocker dans SQLite
|
||||
- Retourner via API pour affichage
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
- [ ] 10. **Créer page HTML watchlist.html**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `templates/watchlist.html`
|
||||
- Même structure que `index.html`
|
||||
- Sections :
|
||||
- Header avec stats
|
||||
- Liste des animes (cards)
|
||||
- Zone de notifications
|
||||
- Modal pour les détails
|
||||
|
||||
**Agent recommandé** : `visual-engineering`
|
||||
|
||||
**QA Scenarios** :
|
||||
```
|
||||
Scenario: La page se charge
|
||||
Tool: playwright
|
||||
Navigate: http://127.0.0.1:3000/watchlist
|
||||
Expected: Titre "Ma Watchlist" affiché
|
||||
```
|
||||
|
||||
- [ ] 11. **Créer watchlist-ui.js**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `static/js/watchlist/main.js`
|
||||
- Fonctions :
|
||||
- `loadWatchlist()` - Charger la liste
|
||||
- `renderWatchlist(items)` - Afficher les cards
|
||||
- `addAnime(animeData)` - Ajouter un anime
|
||||
- `removeAnime(id)` - Retirer
|
||||
- `downloadEpisode(itemId, episode)` - Télécharger
|
||||
- `loadNotifications()` - Charger les notifs
|
||||
- `renderNotifications(notifs)` - Afficher
|
||||
- `markAsRead(id)` - Marquer lu
|
||||
- Appels API vers les endpoints créés
|
||||
|
||||
**Agent recommandé** : `visual-engineering`
|
||||
|
||||
- [ ] 12. **Ajouter CSS**
|
||||
|
||||
**Quoi faire** :
|
||||
- Créer `static/css/watchlist.css`
|
||||
- Style cohérent avec `style.css` existant
|
||||
- Cards, badges, buttons, notifications
|
||||
|
||||
**Agent recommandé** : `visual-engineering`
|
||||
|
||||
- [ ] 13. **Ajouter routes pour servir la page**
|
||||
|
||||
**Quoi faire** :
|
||||
- Ajouter route `GET /watchlist` dans main.py
|
||||
- Servir le template
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
- [ ] 14-17. **Tests d'intégration**
|
||||
|
||||
**Quoi faire** :
|
||||
- Tester le flux complet :
|
||||
1. Ajouter un anime via API
|
||||
2. Voir dans la liste
|
||||
3. Télécharger un épisode manuellement
|
||||
4. Recevoir une notification
|
||||
- Tester l'auto-download (simuler un nouvel épisode)
|
||||
|
||||
**Agent recommandé** : `unspecified-high`
|
||||
|
||||
- [ ] 18. **Nettoyer l'ancien code**
|
||||
|
||||
**Quoi faire** :
|
||||
- Supprimer `app/watchlist.py` (l'ancien)
|
||||
- Supprimer les fichiers JSON `config/watchlist*.json`
|
||||
- Mettre à jour les imports
|
||||
|
||||
**Agent recommandé** : `quick`
|
||||
|
||||
---
|
||||
|
||||
## Stratégie de Vérification
|
||||
|
||||
### Test Manual (Agent QA)
|
||||
|
||||
**Scenario: Ajout d'un anime**
|
||||
```
|
||||
1. Ouvrir /watchlist
|
||||
2. Cliquer "Ajouter un anime"
|
||||
3. Rechercher "Frieren"
|
||||
4. Sélectionner un résultat
|
||||
5. Cliquer "Suivre"
|
||||
Expected: L'anime apparaît dans la liste
|
||||
```
|
||||
|
||||
**Scenario: Téléchargement manuel**
|
||||
```
|
||||
1. Dans la watchlist, cliquer sur un anime
|
||||
2. Voir la liste des épisodes
|
||||
3. Cliquer "Télécharger" sur épisode 1
|
||||
4. Vérifier dans /downloads
|
||||
Expected: Le téléchargement commence
|
||||
```
|
||||
|
||||
**Scenario: Auto-download**
|
||||
```
|
||||
1. Ajouter un anime avec auto-download activé
|
||||
2. Simuler l'apparition d'un nouvel épisode (via scheduler)
|
||||
3. Vérifier dans les downloads
|
||||
Expected: L'épisode est téléchargé automatiquement
|
||||
```
|
||||
|
||||
**Scenario: Notification**
|
||||
```
|
||||
1. Un nouvel épisode est détecté
|
||||
2. Une notification apparaît
|
||||
3. Cliquer sur la notification
|
||||
Expected: Redirection vers l'épisode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Critères de Succès
|
||||
|
||||
- [ ] La base SQLite est créée et fonctionnelle
|
||||
- [ ] Les animes peuvent être ajoutés/retirés de la watchlist
|
||||
- [ ] Les épisodes peuvent être téléchargés manuellement
|
||||
- [ ] Le scheduler vérifie automatiquement les nouveaux épisodes
|
||||
- [ ] L'auto-téléchargement fonctionne
|
||||
- [ ] Les notifications sont créées et affichées
|
||||
- [ ] L'interface est cohérente avec le reste du site
|
||||
- [ ] L'ancien code est nettoyé
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- Wave 1: `feat(watchlist): add SQLite database and models`
|
||||
- Wave 2: `feat(watchlist): add API routes and scheduler`
|
||||
- Wave 3: `feat(watchlist): add frontend UI`
|
||||
- Wave 4: `feat(watchlist): integrate and test`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Le système de download existant (`download_manager`) est réutilisé
|
||||
- Les providers existants (anime-sama, vostfree, etc.) sont réutilisés
|
||||
- Le système de notification est simple (in-app) pour éviter les dépendances supplémentaires
|
||||
- Le scheduler utilise APScheduler déjà présent dans le projet
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user