c6be191699
## Backend Implementation (100% Complete)
### Core Components
- **WatchlistManager**: JSON-based storage with full CRUD operations
- User-scoped data access for multi-tenant support
- Statistics and query functions
- Settings management with persistence
- **EpisodeChecker**: Automatic new episode detection
- Checks for new episodes using existing downloaders
- Automatic download with error handling
- Manual and scheduled check support
- Lazy initialization to avoid circular imports
- **AutoDownloadScheduler**: APScheduler-based periodic checking
- Configurable intervals (1-168 hours)
- Start/stop/restart controls
- Next run time tracking
### API Endpoints (15 endpoints)
- POST /api/watchlist - Add anime to watchlist
- GET /api/watchlist - Get user's watchlist (with status filter)
- GET /api/watchlist/{id} - Get specific item
- PUT /api/watchlist/{id} - Update item
- DELETE /api/watchlist/{id} - Delete item
- POST /api/watchlist/{id}/check - Check for new episodes
- POST /api/watchlist/{id}/pause - Pause tracking
- POST /api/watchlist/{id}/resume - Resume tracking
- GET /api/watchlist/settings - Get settings
- PUT /api/watchlist/settings - Update settings
- GET /api/watchlist/stats - Get statistics
- POST /api/watchlist/check-all - Check all items
- GET /api/watchlist/scheduler/status - Scheduler status
- POST /api/watchlist/scheduler/start - Start scheduler
- POST /api/watchlist/scheduler/stop - Stop scheduler
### Bug Fixes
- Fixed WatchlistManager.update() to accept both dict and WatchlistItemUpdate
- Added asyncio import to AutoDownloadScheduler for event loop detection
- Improved scheduler start() with better error handling
## Frontend Implementation (100% Complete)
### UI Components
- **Watchlist Page** (/watchlist)
- Scheduler status panel with start/stop/check all buttons
- Filter tabs (all/active/paused/completed)
- Statistics display with color-coded cards
- Watchlist items with pause/resume/delete controls
- Auto-refresh every 30 seconds
- Authentication check
- **Settings Modal**
- Check interval configuration (1-168h)
- Auto-download toggle
- Max concurrent downloads slider
- Notifications toggle
- Live settings update with scheduler restart
- **"Suivre" Button**
- Added to anime search result cards
- Purple gradient with heart icon
- Quick-add to watchlist functionality
- State tracking (disabled when already in watchlist)
### JavaScript Files
- **static/js/watchlist.js**: API client functions
- All watchlist API calls with token auth
- Error handling and response parsing
- **static/js/watchlist-ui.js**: UI functions
- Display watchlist with stats
- Handle add/pause/resume/delete
- Filter by status
- Settings modal management
- **static/js/tabs.js**: Watchlist tab handler
- Redirects to /watchlist page
## Testing
### Test Suite (test_watchlist_simple.py)
All tests passing (3/3):
1. **Watchlist Manager Tests** ✅
- Create/read/update/delete operations
- User-scoped queries
- Statistics generation
- Check time updates
2. **Settings Tests** ✅
- Get current settings
- Update settings with validation
- Reset to defaults
3. **Scheduler Tests** ✅
- Start/stop/restart controls
- Running status verification
- Next run time tracking
### Dependencies
- APScheduler 3.11.0 installed in virtual environment
- tzlocal 5.3.1 (APScheduler dependency)
## Documentation
- docs/WATCHLIST_AUTO_DOWNLOAD.md: Complete system documentation
- API endpoints with examples
- Architecture overview
- Usage examples
- Troubleshooting guide
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
55 lines
3.9 KiB
HTML
55 lines
3.9 KiB
HTML
<h1>⚡ Ohm Stream Downloader</h1>
|
|
<p class="subtitle">Téléchargez vos vidéos, animes et séries depuis vos hébergeurs préférés</p>
|
|
|
|
<!-- User info and logout button -->
|
|
<div id="userInfo" style="display: none; margin-bottom: 15px; padding: 10px; background: rgba(0, 217, 255, 0.1); border-radius: 8px; justify-content: space-between; align-items: center;">
|
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
<span style="color: #00d9ff;">👤</span>
|
|
<span style="color: #fff; font-size: 14px;">Connecté en tant que <strong id="currentUser" style="color: #00d9ff;">-</strong></span>
|
|
</div>
|
|
<button class="btn-secondary btn-small" onclick="handleLogout()" style="padding: 5px 15px; font-size: 12px;">
|
|
🚪 Déconnexion
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Login prompt (shown when not logged in) -->
|
|
<div id="loginPrompt" style="display: none; margin-bottom: 15px; padding: 15px; background: rgba(0, 217, 255, 0.1); border: 1px solid rgba(0, 217, 255, 0.3); border-radius: 8px; text-align: center;">
|
|
<p style="color: #00d9ff; margin: 0 0 10px 0;">👋 Bienvenue! <a href="/login" style="color: #00d9ff; text-decoration: underline;">Connectez-vous</a> pour télécharger des vidéos</p>
|
|
</div>
|
|
|
|
<!-- Tabs - Hidden by default, shown only when authenticated -->
|
|
<div id="mainTabs" class="tabs" style="visibility: hidden;">
|
|
<button class="tab active" data-tab-type="home" onclick="switchTab('home')">
|
|
<svg style="width:16px;height:16px;vertical-align:middle;margin-right:5px" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
|
|
</svg>
|
|
Accueil
|
|
</button>
|
|
<button class="tab" data-tab-type="anime" onclick="switchTab('anime')">
|
|
<svg style="width:16px;height:16px;vertical-align:middle;margin-right:5px" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
Anime
|
|
</button>
|
|
<button class="tab" data-tab-type="series" onclick="switchTab('series')">
|
|
<svg style="width:16px;height:16px;vertical-align:middle;margin-right:5px" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z"></path>
|
|
</svg>
|
|
Série
|
|
</button>
|
|
<button class="tab" data-tab-type="providers" onclick="switchTab('providers')">
|
|
<svg style="width:16px;height:16px;vertical-align:middle;margin-right:5px" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
|
|
</svg>
|
|
Fournisseurs
|
|
</button>
|
|
<button class="tab" data-tab-type="watchlist" onclick="switchTab('watchlist')">
|
|
<svg style="width:16px;height:16px;vertical-align:middle;margin-right:5px" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2m0 0a2 2 0 00-2 2h10a2 2 0 002-2V7a2 2 0 00-2-2"></path>
|
|
</svg>
|
|
Watchlist
|
|
</button>
|
|
<!-- Provider tabs will be loaded dynamically after the static tabs -->
|
|
</div>
|