chore: update watchlist features and fixes

This commit is contained in:
root
2026-02-28 09:22:57 +00:00
parent 4c96d0c1c5
commit 20bcc75b9b
64 changed files with 5193 additions and 77 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

+1
View File
@@ -0,0 +1 @@
364: window.watchlistTabLoaded = false;
+16
View File
@@ -0,0 +1,16 @@
# Evidence: Task 1 - Timeout URL Test
## Scenario: Invalid video URL times out
**Tool**: Python3
**Preconditions**: URL that times out (httpbin.org/delay/20)
**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)
**Actual Result**:
Video URL validation FAILED: Timeout for https://httpbin.org/delay/20...
Result for timeout URL: False
**Status**: PASS
+15
View File
@@ -0,0 +1,15 @@
# Evidence: Task 1 - Valid URL Test
## Scenario: Valid video URL returns 200 OK
**Tool**: Python3
**Preconditions**: URL that returns HTTP 200
**Steps**:
1. python3 -c "from app.downloaders.anime_sites.animesama import AnimeSamaDownloader; d = AnimeSamaDownloader(); result = d._test_video_url('https://www.google.com/'); print(f'Result: {result}')"
**Expected Result**: Returns True
**Actual Result**:
Result for google.com: True
**Status**: PASS
+16
View File
@@ -0,0 +1,16 @@
# Evidence: Task 2 - All Players Fail
## Scenario: All players fail
**Tool**: Python3
**Preconditions**: Mock all extractions to fail
**Steps**:
1. Mock all _extract_from_* methods to raise Exception
2. Call get_download_link_with_fallback()
**Expected Result**: Raises exception "All video players failed"
**Actual Result**:
Exception raised: All players failed. Last error: Player failed
**Status**: PASS
@@ -0,0 +1,42 @@
# CSS Class Conflicts Check Results
## Check 4: filter-tab class in style.css
No matches found for "filter-tab" in static/css/style.css
However, filter-tab IS defined in watchlist.html inline styles:
/opt/Ohm_streaming/templates/watchlist.html
123: .filter-tabs {
130: .filter-tab {
140: .filter-tab:hover {
144: .filter-tab.active {
257: <div class="filter-tabs">
258: <button class="filter-tab active" onclick="filterWatchlist('all', this)">Tous</button>
259: <button class="filter-tab" onclick="filterWatchlist('active', this)">Actifs</button>
260: <button class="filter-tab" onclick="filterWatchlist('paused', this)">En pause</button>
261: <button class="filter-tab" onclick="filterWatchlist('completed', this)">Terminés</button>
363: document.querySelectorAll('.filter-tab').forEach(tab => {
## Check 5: .tab class in style.css
Found 2 matches in static/css/style.css
151: .tab {
733: .tab {
## Tab Class Usage Across Templates:
- login.html: auth-tabs, auth-tab
- watchlist.html: .tab (navigation), .filter-tabs, .filter-tab
- components/header.html: .tab (navigation tabs)
## Potential CSS Conflict Analysis:
1. filter-tab: Defined inline in watchlist.html, NOT in style.css
- Risk: LOW (isolated to watchlist page)
2. .tab: Defined in style.css at lines 151 and 733
- Used in multiple templates for navigation tabs
- .filter-tab is DIFFERENT from .tab
- Risk: LOW (.tab and .filter-tab are distinct classes)
## Conclusion:
NO CSS CLASS CONFLICTS DETECTED
- filter-tab is isolated to watchlist.html (inline CSS)
- .tab class in style.css is for main navigation tabs
- .filter-tab is a separate, distinct class for watchlist filtering
@@ -0,0 +1,32 @@
# DOM ID Conflicts Check Results
## Check 1: watchlistContainer & schedulerStatus
Found 2 matches in 1 file(s):
/opt/Ohm_streaming/templates/watchlist.html
233: <div class="scheduler-status" id="schedulerStatus">
265: <div id="watchlistContainer">
## Check 2: settingsModal & nextRunInfo
Found 2 matches in 1 file(s):
/opt/Ohm_streaming/templates/watchlist.html
237: <div id="nextRunInfo" class="next-run-info">Chargement...</div>
422: <div id="settingsModal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 1000;">
## Check 3: startSchedulerBtn & stopSchedulerBtn
Found 2 matches in 1 file(s):
/opt/Ohm_streaming/templates/watchlist.html
240: <button id="startSchedulerBtn" class="btn-primary btn-small" onclick="handleStartScheduler()" style="display:none;">
243: <button id="stopSchedulerBtn" class="btn-secondary btn-small" onclick="handleStopScheduler()" style="display:none;">
## Conflict Analysis:
All IDs are unique to watchlist.html only. No conflicts found with other templates.
Checked templates:
- login.html (auth-tabs, auth-tab)
- index.html (tab-anime, tab-series, tab-providers)
- components/header.html (mainTabs, tab-home, tab-anime, tab-series, etc.)
- components/home_section.html (tab-home)
- watchlist.html (these IDs are local to this file)
## Conclusion:
NO DOM ID CONFLICTS DETECTED
+19
View File
@@ -0,0 +1,19 @@
# Evidence: Task 2 - First Player Works
## Scenario: First player (VidMoly) works
**Tool**: Python3
**Preconditions**: Mock VidMoly URL that passes validation
**Steps**:
1. Mock _test_video_url to return True
2. Mock _extract_from_vidmoly to return valid URL
3. Call get_download_link_with_fallback()
**Expected Result**: Returns VidMoly URL, logs "VidMoly player succeeded"
**Actual Result**:
Video URL: https://vidmoly.to/video.mp4
Filename: vidmoly_video.mp4
Used player: VidMoly
**Status**: PASS
@@ -0,0 +1,19 @@
# Evidence: Task 2 - Second Player Works
## Scenario: First player fails, second works
**Tool**: Python3
**Preconditions**: Mock VidMoly to fail, SendVid to succeed
**Steps**:
1. Mock _extract_from_vidmoly to raise Exception
2. Mock _extract_from_sendvid to return valid URL
3. Mock _test_video_url to return True
4. Call get_download_link_with_fallback()
**Expected Result**: Returns SendVid URL (VidMoly failed, SendVid succeeded)
**Actual Result**:
Video URL: https://sendvid.com/video.mp4
Used player: SendVid
**Status**: PASS
+17
View File
@@ -0,0 +1,17 @@
# Evidence: Task 3 - Direct URL Skips Fallback
## Scenario: Direct video URL skips fallback
**Tool**: Python3
**Preconditions**: Anime-Sama downloader with fallback method
**Steps**:
1. Mock get_download_link_with_fallback
2. Call get_download_link() with direct URL (no pipe)
**Expected Result**: Fallback method is NOT called (False) - direct extraction used
**Actual Result**:
Fallback called: False
Result: ('https://direct.mp4', 'direct.mp4')
**Status**: PASS
+16
View File
@@ -0,0 +1,16 @@
# Evidence: Task 3 - Pipe URL Triggers Fallback
## Scenario: Pipe URL triggers fallback
**Tool**: Python3
**Preconditions**: Anime-Sama downloader with fallback method
**Steps**:
1. Mock get_download_link_with_fallback
2. Call get_download_link() with pipe URL
**Expected Result**: Fallback method is called (True)
**Actual Result**:
Fallback called: True
**Status**: PASS
+93
View File
@@ -0,0 +1,93 @@
# JavaScript Duplication Audit Report
**Generated:** 2026-02-26
**Scope:** static/js/**/*.js (13 files)
**Files Audited:** api.js, utils.js, auth.js, main.js, tabs.js, anime.js, series-search.js, downloads.js, watchlist/main.js, anime-details.js, recommendations.js, watchlist.js, watchlist-ui.js
---
## CRITICAL DUPLICATIONS (Potential Syntax Errors)
### 1. translateStatus() Function - DUPLICATED DEFINITION
- **File 1:** `static/js/utils.js:35` - Primary definition
- **File 2:** `static/js/anime-details.js:428` - Duplicate definition
**Impact:** HIGH - If both files are loaded, the second definition will overwrite the first, causing unpredictable behavior. The utils.js version is used by downloads.js and recommendations.js, while anime-details.js has its own localized version.
**Recommendation:** Remove duplicate in anime-details.js and ensure anime-details.js imports from utils.js
---
## MINOR DUPLICATIONS (Non-Breaking)
### 2. Redundant const Declarations in Same Function Scope (Different Functions)
#### auth.js - Duplicate variable declarations across functions
- `mainContent` declared at line 70 and line 76 (in different functions showMainContent/hideMainContent)
- `userInfo` declared at line 57 and line 82 (in showUserInfo/showLoginPrompt)
- `loginPrompt` declared at line 58 and line 83
- `mainTabs` declared at line 59 and line 84
**Impact:** LOW - These are in different function scopes, not causing syntax errors but creating redundant code
#### recommendations.js - Duplicate variable names in different functions
- `container` declared at lines 5, 54, 105 (in different functions)
- `section` declared at lines 6, 55 (in different functions)
**Impact:** LOW - Different function scopes
#### tabs.js - Duplicate container variable
- `container` declared at lines 115, 152, 160, 178, 186, 235, 252, 329
**Impact:** LOW - Different function scopes
#### anime.js - Duplicate variable names across functions
- `selectElement` declared at lines 156, 245, 253, 261, 307, 352
- `seasonSelectElement` declared at lines 156, 245
- `actionsDiv` declared at lines 287, 325
**Impact:** LOW - Different function scopes
---
## PATTERN OBSERVATIONS
### Utility Functions Shared Across Files
The following functions are defined once but used across multiple files:
- `escapeHtml()` - Defined in utils.js:26, used in 8 files
- `translateStatus()` - DEFINED TWICE (CRITICAL ISSUE)
- `formatBytes()` - Defined in utils.js
- `formatSpeed()` - Defined in utils.js
- `extractSeriesName()` - Defined in utils.js
- `getDayString()` - Defined in utils.js
### Cross-File Function Usage
- `renderReleaseCard()` - Defined in recommendations.js:195, called in tabs.js:171
- `renderAnimeCard()` - Defined in anime.js:58, called in anime-details.js
- `loadDownloads()` - Defined in downloads.js, called from multiple files
---
## SUMMARY
| Severity | Count | Issue |
|----------|-------|-------|
| CRITICAL | 1 | translateStatus() defined twice (utils.js + anime-details.js) |
| MINOR | 4+ | Redundant const declarations across functions (auth.js) |
| MINOR | 3+ | Duplicate container/section variables (recommendations.js, tabs.js, anime.js) |
---
## RECOMMENDATIONS
1. **FIX CRITICAL:** Remove duplicate `translateStatus()` from anime-details.js and use the version from utils.js
2. **Consider:** Consolidating utility functions into a single utils module that all files import
3. **Future Cleanup:** Review auth.js for redundant variable declarations (minor optimization)
---
## VERIFICATION
Audit completed: 13 JavaScript files scanned
Duplicate function definitions: 1 CRITICAL
Redundant const declarations: Multiple (non-critical)
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

+111
View File
@@ -0,0 +1,111 @@
# Task 5: Watchlist API Structure Documentation
## Base URL
`http://localhost:3000/api/watchlist`
## Available Endpoints
### 1. GET /api/watchlist
- **Description**: List all watchlist items for current user
- **Auth**: Required (JWT Bearer token)
- **Query Params**:
- `status` (optional): Filter by status (active, paused, completed, archived)
- **Response 200**: `{"watchlist": [], "total": 0, "filters": {"status": null}}`
- **Response 403**: `{"detail": "Not authenticated"}`
### 2. POST /api/watchlist
- **Description**: Add a new anime to the watchlist
- **Auth**: Required
- **Body**:
```json
{
"anime_title": "string",
"anime_url": "string",
"provider_id": "string",
"lang": "vostfr",
"auto_download": true,
"quality_preference": "auto",
"poster_image": "string (optional)",
"cover_image": "string (optional)",
"synopsis": "string (optional)",
"genres": ["string"] (optional)
}
```
- **Response**: `{"status": "added", "item": {...}}`
### 3. GET /api/watchlist/{item_id}
- **Description**: Get details of a specific watchlist item
- **Auth**: Required
- **Response 200**: `{"item": {...}}`
- **Response 404**: `{"detail": "Watchlist item not found"}`
- **Response 403**: `{"detail": "Access denied"}`
### 4. PUT /api/watchlist/{item_id}
- **Description**: Update a watchlist item
- **Auth**: Required
- **Response**: `{"status": "updated", "item": {...}}`
### 5. DELETE /api/watchlist/{item_id}
- **Description**: Remove an anime from the watchlist
- **Auth**: Required
- **Response**: `{"status": "deleted", "item_id": "string"}`
### 6. GET /api/watchlist/{item_id}/episodes
- **Description**: Get all downloaded episodes for a watchlist item
- **Auth**: Required
### 7. POST /api/watchlist/{item_id}/download/{episode}
- **Description**: Download a specific episode
- **Auth**: Required
- **Response**: `{"status": "downloading", "task_id": "string", "episode": int, "item_id": "string"}`
### 8. GET /api/watchlist/stats ⚠️ BUG
- **Description**: Get watchlist statistics
- **Auth**: Required
- **Expected Response**:
```json
{
"total": 0,
"active": 0,
"paused": 0,
"completed": 0,
"archived": 0,
"auto_download_enabled": 0,
"total_episodes_downloaded": 0,
"providers": {}
}
```
- **Actual Response**: 404 "Watchlist item not found" (ROUTING BUG)
### 9. GET /api/watchlist/settings ⚠️ BUG
- **Description**: Get watchlist settings
- **Auth**: Required
- **Expected Response**: `{"settings": {...}}`
- **Actual Response**: 404 "Watchlist item not found" (ROUTING BUG)
### 10. GET /api/watchlist/notifications ⚠️ BUG
- **Description**: Get user notifications
- **Auth**: Required
- **Query Params**: `unread_only` (bool)
- **Expected Response**: `{"notifications": [], "total": 0, "unread_only": false}`
- **Actual Response**: 404 "Watchlist item not found" (ROUTING BUG)
### 11. PUT /api/watchlist/notifications/{notification_id}/read
- **Description**: Mark a notification as read
- **Auth**: Required
### 12. PUT /api/watchlist/settings
- **Description**: Update watchlist settings
- **Auth**: Required
## Authentication
- Uses JWT Bearer tokens
- Token obtained from POST /api/auth/login
- Pass as: `Authorization: Bearer <token>`
## Bug Summary
- **Issue**: 3 endpoints return 404 instead of correct responses
- **Affected**: /stats, /settings, /notifications
- **Cause**: Route ordering - `/{item_id}` catch-all defined before these specific routes
- **Location**: app/routes/watchlist.py
- **Fix needed**: Move specific routes BEFORE the `/{item_id}` route
@@ -0,0 +1,19 @@
# Evidence: Task 5 - Integration Test with Real Anime-Sama URL
## Scenario: Download Frieren S1 E1 with fallback
**Tool**: curl + API
**Preconditions**: Server running, fallback implemented
**Steps**:
1. Get episodes from anime-sama.tv
2. Download episode via API
**Expected Result**: Download completes successfully
**Actual Result**:
- Download status: COMPLETED
- File size: 321MB
- File: downloads/Frieren - S1 - Episode 01.mp4
- Logs show: Using SendVid for extraction (fallback working)
**Status**: PASS
@@ -0,0 +1,41 @@
# Task 5: GET /api/watchlist Test Results
## Test Date: 2026-02-26
## Server Status
- Server running on port 3000: ✓
- Health check: ✓ PASS
## Authentication Test
- Unauthenticated request to /api/watchlist:
- HTTP Status: 403
- Response: {"detail":"Not authenticated"}
- Authenticated request to /api/watchlist:
- HTTP Status: 200
- Response: {"watchlist":[],"total":0,"filters":{"status":null}}
## Endpoints Tested
| Endpoint | Auth | Expected Status | Actual Status | Result |
|----------|------|-----------------|---------------|--------|
| GET /api/watchlist | No | 401/403 | 403 | ✓ PASS |
| GET /api/watchlist | Yes | 200 | 200 | ✓ PASS |
| GET /api/watchlist/stats | Yes | 200 | 404 | ✗ FAIL (BUG) |
| GET /api/watchlist/settings | Yes | 200 | 404 | ✗ FAIL (BUG) |
| GET /api/watchlist/notifications | Yes | 200 | 404 | ✗ FAIL (BUG) |
## Issue Found
The following endpoints return 404 "Watchlist item not found" when they should work:
- /api/watchlist/stats
- /api/watchlist/settings
- /api/watchlist/notifications
**Root Cause**: Route ordering issue in `app/routes/watchlist.py`
- The `/{item_id}` catch-all route (line 134) is defined BEFORE the specific routes like `/stats` (line 372), `/settings` (line 335), and `/notifications` (line 285)
- FastAPI matches these paths as item IDs instead of the intended routes
## Test User
- Username: watchlist_test
- Token: JWT (7-day expiry)
+14
View File
@@ -0,0 +1,14 @@
Watchlist Integration Test Results
============================================================
[PASS] Navigate to /watchlist
[PASS] Watchlist tab highlighted
[PASS] Header/nav present
[PASS] Scheduler panel displays
[PASS] Filter tabs present and clickable
[PASS] Settings modal works
[PASS] Refresh mechanism present
[PASS] Tab switching works
[PASS] /web#watchlist loads watchlist
[PASS] /watchlist page has content
============================================================
Total: 10/10 tests passed
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB