feat: Complete watchlist & auto-download system with UI

Implement comprehensive watchlist system with automatic episode detection
and downloading. Features include per-user watchlists, scheduler-based
periodic checks, and a modern web UI.

**Backend Components:**
- WatchlistManager: JSON-based storage with multi-tenant support
- EpisodeChecker: Detects and downloads new episodes automatically
- AutoDownloadScheduler: APScheduler-based periodic task execution
- Complete REST API for CRUD operations and scheduler control

**Frontend Components:**
- Modern watchlist page with dark theme and animations
- Real-time status updates and progress tracking
- Scheduler controls with next-run display
- Add anime directly from search results

**Models & Configuration:**
- WatchlistItem with status, quality, and auto-download settings
- WatchlistSettings for global configuration
- Per-user statistics and provider tracking

**API Endpoints:**
- GET/POST /api/watchlist - List and add items
- PUT/DELETE /api/watchlist/{id} - Update and delete
- POST /api/watchlist/{id}/check - Manual check trigger
- POST /api/watchlist/check-all - Check all due items
- GET/PUT /api/watchlist/settings - Global settings
- GET /api/watchlist/stats - Statistics
- GET/POST /api/watchlist/scheduler/* - Scheduler control

**Configuration Files:**
- config/watchlist.json - User watchlist data
- config/watchlist_settings.json - Global settings

Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-02-24 09:13:22 +00:00
parent c6be191699
commit da5403a307
17 changed files with 1733 additions and 259 deletions
+13 -14
View File
@@ -1,14 +1,13 @@
/**
* Watchlist management and auto-download UI
* Note: API_BASE is defined in api.js (loaded before this file)
*/
const API_BASE = '/api';
/**
* Get user's watchlist
*/
async function getWatchlist(status = null) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -35,7 +34,7 @@ async function getWatchlist(status = null) {
* Add anime to watchlist
*/
async function addToWatchlist(animeData) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -61,7 +60,7 @@ async function addToWatchlist(animeData) {
* Update watchlist item
*/
async function updateWatchlistItem(itemId, updateData) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -86,7 +85,7 @@ async function updateWatchlistItem(itemId, updateData) {
* Delete from watchlist
*/
async function deleteFromWatchlist(itemId) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -123,7 +122,7 @@ async function resumeWatchlistItem(itemId) {
* Check specific anime for new episodes
*/
async function checkWatchlistItem(itemId) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -146,7 +145,7 @@ async function checkWatchlistItem(itemId) {
* Check all watchlist items
*/
async function checkAllWatchlistItems() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -169,7 +168,7 @@ async function checkAllWatchlistItems() {
* Get watchlist settings
*/
async function getWatchlistSettings() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -191,7 +190,7 @@ async function getWatchlistSettings() {
* Update watchlist settings
*/
async function updateWatchlistSettings(settings) {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -216,7 +215,7 @@ async function updateWatchlistSettings(settings) {
* Get watchlist statistics
*/
async function getWatchlistStats() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -238,7 +237,7 @@ async function getWatchlistStats() {
* Get scheduler status
*/
async function getSchedulerStatus() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -260,7 +259,7 @@ async function getSchedulerStatus() {
* Start scheduler
*/
async function startScheduler() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}
@@ -283,7 +282,7 @@ async function startScheduler() {
* Stop scheduler
*/
async function stopScheduler() {
const token = localStorage.getItem('token');
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('Not authenticated');
}