diff --git a/main.py b/main.py
index 82fc136..ae4abc8 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,5 @@
from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Query, Request, Depends, status
-from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response
+from fastapi.responses import StreamingResponse, FileResponse, JSONResponse, Response, RedirectResponse
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
@@ -359,7 +359,29 @@ async def login_page(request: Request):
@app.get("/watchlist")
-async def watchlist_page(request: Request):
+async def watchlist_redirect():
+ """Redirect /watchlist to web interface with watchlist hash"""
+ return RedirectResponse("/web#watchlist")
+#JJ|# API Endpoints
+
+#WY|@app.post("/api/download")
+#JJ|# API Endpoints
+
+#WY|@app.post("/api/download")
+async def create_download(request: DownloadRequest, background_tasks: BackgroundTasks):
+#JJ|# API Endpoints
+
+#WY|@app.post("/api/download")
+#JJ|# API Endpoints
+
+#WY|@app.post("/api/download")
+
+#JJ|# API Endpoints
+
+#WY|@app.post("/api/download")
+#JJ|# API Endpoints
+#JJ|# API Endpoints
+
"""Watchlist management page"""
return templates.TemplateResponse("watchlist.html", {"request": request})
diff --git a/static/css/style.css b/static/css/style.css
index 55b11ec..d026bf7 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -1366,3 +1366,190 @@
justify-content: flex-start;
}
}
+
+/* ===================================
+ Watchlist Page Styles
+ =================================== */
+
+.watchlist-body {
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
+ min-height: 100vh;
+ color: #e0e0e0;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.watchlist-header {
+ background: rgba(217, 255, 0.1);
+ border: 1px solid rgba(0, 217, 255, 0.3);
+ border-radius: 12px;
+ padding: 30px;
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.watchlist-header h1 {
+ color: #00d9ff;
+ margin: 0 0 10px 0;
+ font-size: 28px;
+ font-weight: 600;
+}
+
+.watchlist-header p {
+ color: #999;
+ margin: 0;
+ font-size: 14px;
+}
+
+.watchlist-controls {
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+ margin-bottom: 30px;
+ flex-wrap: wrap;
+}
+
+.watchlist-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px 40px;
+}
+
+.scheduler-status {
+ background: rgba(0, 217, 255, 0.05);
+ border: 1px solid rgba(0, 217, 255, 0.2);
+ border-radius: 10px;
+ padding: 20px;
+ margin-bottom: 30px;
+}
+
+.scheduler-status-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.scheduler-status h3 {
+ margin: 0;
+ color: #00d9ff;
+ font-size: 18px;
+}
+
+.scheduler-controls {
+ display: flex;
+ gap: 10px;
+}
+
+.status-indicator {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 5px 12px;
+ border-radius: 12px;
+ font-size: 13px;
+}
+
+.status-indicator.running {
+ background: rgba(76, 175, 80, 0.2);
+ color: #4caf50;
+}
+
+.status-indicator.stopped {
+ background: rgba(244, 67, 54, 0.2);
+ color: #f44;
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+}
+
+.status-dot.running {
+ background: #4caf50;
+ animation: watchlist-pulse 2s infinite;
+}
+
+.status-dot.stopped {
+ background: #f44;
+}
+
+@keyframes watchlist-pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.next-run-info {
+ font-size: 13px;
+ color: #999;
+ margin-top: 10px;
+}
+
+.filter-tabs {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 20px;
+ justify-content: center;
+}
+
+.filter-tab {
+ padding: 8px 16px;
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+ color: #ccc;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.filter-tab:hover {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.filter-tab.active {
+ background: rgba(0, 217, 255, 0.2);
+ border-color: rgba(0, 217, 255, 0.5);
+ color: #00d9ff;
+}
+
+.watchlist-loading {
+ text-align: center;
+ padding: 60px;
+ color: #999;
+}
+
+.empty-watchlist {
+ text-align: center;
+ padding: 80px 20px;
+}
+
+.watchlist-error-message {
+ text-align: center;
+ padding: 40px;
+ color: #f44;
+}
+
+.watchlist-item {
+ transition: all 0.3s ease;
+}
+
+.watchlist-item:hover {
+ background: rgba(255, 255, 255, 0.08);
+ transform: translateY(-2px);
+}
+
+.watchlist-btn-small {
+ padding: 6px 12px;
+ font-size: 12px;
+}
+
+.watchlist-header-back-btn {
+ margin-top: 15px;
+}
+
+.watchlist-modal-action-btn {
+ flex: 1;
+ padding: 12px;
+ font-size: 14px;
+ cursor: pointer;
+}
diff --git a/static/js/main.js b/static/js/main.js
index a4ec5f4..d0a0f56 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -228,6 +228,9 @@ function switchTab(tabName) {
} else if (tabType === 'providers' && tabName === 'providers') {
// Static providers tab
btn.classList.add('active');
+ } else if (tabType === 'watchlist' && tabName === 'watchlist') {
+ // Static watchlist tab
+ btn.classList.add('active');
} else if (tabType === 'anime' && btn.getAttribute('data-provider') === tabName.replace('anime-', '')) {
btn.classList.add('active');
} else if (tabType === 'series' && btn.getAttribute('data-provider') === tabName.replace('series-', '')) {
diff --git a/static/js/tabs.js b/static/js/tabs.js
index a720422..f27edc5 100644
--- a/static/js/tabs.js
+++ b/static/js/tabs.js
@@ -147,6 +147,7 @@ async function loadSeriesRecommendations() {
} else {
container.innerHTML = '
Aucune recommandation trouvée
';
}
+
} catch (error) {
console.error('Error loading series recommendations:', error);
const container = document.getElementById('seriesRecommendationsList');
@@ -360,10 +361,22 @@ window.showDownloadInfo = showDownloadInfo;
document.addEventListener('DOMContentLoaded', () => {
// Wait for main.js to be loaded
setTimeout(() => {
+ // Watchlist auto-refresh interval
+ let watchlistRefreshInterval = null;
+
+ // Initialize watchlist tab flag
+ window.watchlistTabLoaded = false;
+
// Override switchTab to load content when opening new tabs
const originalSwitchTab = window.switchTab;
if (originalSwitchTab) {
window.switchTab = function(tabName) {
+ // Clear watchlist interval when switching away from watchlist
+ if (tabName !== 'watchlist' && watchlistRefreshInterval) {
+ clearInterval(watchlistRefreshInterval);
+ watchlistRefreshInterval = null;
+ }
+
// Call original switchTab first
originalSwitchTab(tabName);
@@ -386,8 +399,26 @@ document.addEventListener('DOMContentLoaded', () => {
window.providersTabLoaded = true;
}
} else if (tabName === 'watchlist') {
- // Watchlist is handled by its own page
- window.location.href = '/watchlist';
+ // Clear any existing interval before starting new one
+ if (watchlistRefreshInterval) {
+ clearInterval(watchlistRefreshInterval);
+ watchlistRefreshInterval = null;
+ }
+
+ if (!window.watchlistTabLoaded) {
+ // Load watchlist content
+ if (typeof displayWatchlist === 'function') {
+ displayWatchlist();
+ }
+ window.watchlistTabLoaded = true;
+ }
+
+ // Start 30-second auto-refresh interval for watchlist
+ if (typeof displayWatchlist === 'function') {
+ watchlistRefreshInterval = setInterval(() => {
+ displayWatchlist();
+ }, 30000);
+ }
}
}, 100);
};
diff --git a/static/js/watchlist-ui.js b/static/js/watchlist-ui.js
index a36848b..d52829a 100644
--- a/static/js/watchlist-ui.js
+++ b/static/js/watchlist-ui.js
@@ -321,6 +321,169 @@ async function handleCheckAll() {
}
}
+
+
+/**
+ * Create settings modal HTML
+ */
+function createSettingsModal(settings) {
+ const modalHtml = `
+
+
+
+
⚙️ Paramètres Watchlist
+
+
+
+
+
+
+
+
+
Entre 1 et 168 heures (1 semaine)
+
+
+
+
+
+
📥 Téléchargement automatique
+
Télécharger automatiquement les nouveaux épisodes
+
+
+
+
+
+
+
+
+
Maximum 5 téléchargements en parallèle
+
+
+
+
+
+
🔔 Notifications
+
Être notifié des nouveaux épisodes
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ return modalHtml;
+}
+
+/**
+ * Close settings modal
+ */
+function closeSettingsModal() {
+ const modal = document.getElementById('settingsModal');
+ if (modal) {
+ modal.remove();
+ }
+}
+
+/**
+ * Save settings
+ */
+async function saveSettings() {
+ try {
+ const checkInterval = parseInt(document.getElementById('checkInterval').value);
+ const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked;
+ const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value);
+ const notifyEnabled = document.getElementById('notifyEnabled').checked;
+
+ const settings = {
+ check_interval_hours: checkInterval,
+ auto_download_enabled: autoDownloadEnabled,
+ max_concurrent_auto_downloads: maxConcurrent,
+ notify_on_new_episodes: notifyEnabled
+ };
+
+ await updateWatchlistSettings(settings);
+
+ // Restart scheduler if it's running to apply new interval
+ const status = await getSchedulerStatus();
+ if (status.running) {
+ await stopScheduler();
+ await startScheduler();
+ }
+
+ closeSettingsModal();
+ alert('✅ Paramètres enregistrés avec succès!');
+ await loadSchedulerStatus();
+
+ } catch (error) {
+ console.error('Error saving settings:', error);
+ alert(`❌ Erreur: ${error.message}`);
+ }
+}
+
// Make functions available globally
window.displayWatchlist = displayWatchlist;
window.handleAddToWatchlist = handleAddToWatchlist;
@@ -329,3 +492,6 @@ window.handleResumeWatchlist = handleResumeWatchlist;
window.handleCheckItem = handleCheckItem;
window.handleDeleteWatchlist = handleDeleteWatchlist;
window.handleCheckAll = handleCheckAll;
+window.createSettingsModal = createSettingsModal;
+window.closeSettingsModal = closeSettingsModal;
+window.saveSettings = saveSettings;
\ No newline at end of file
diff --git a/templates/components/watchlist_section.html b/templates/components/watchlist_section.html
new file mode 100644
index 0000000..669306d
--- /dev/null
+++ b/templates/components/watchlist_section.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Chargement de la watchlist...
+
diff --git a/templates/index.html b/templates/index.html
index 9cab225..fb34c73 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -112,6 +112,10 @@
+
+{% include "components/watchlist_section.html" %}
+
+
{% include "components/downloads_section.html" %}
diff --git a/templates/watchlist.html b/templates/watchlist.html
index 8d70717..e5b7a08 100644
--- a/templates/watchlist.html
+++ b/templates/watchlist.html
@@ -5,204 +5,36 @@
Watchlist - Ohm Stream Downloader
-
-
+
+
+
+
⚡ Ohm Stream Downloader
+
Téléchargez vos vidéos, animes et séries
+
+
+
+
+ 👤 Connecté
+
+
+
+
+
+
+
+
+
+
+
+
@@ -215,16 +47,16 @@
Chargement...
-
@@ -241,7 +73,7 @@
-
Chargement de la watchlist...
+
Chargement de la watchlist...
@@ -394,175 +226,20 @@
async function handleOpenSettings() {
try {
const settings = await getWatchlistSettings();
-
- // Create modal HTML
- const modalHtml = `
-
-
-
-
⚙️ Paramètres Watchlist
- ×
-
-
-
-
-
-
-
-
Entre 1 et 168 heures (1 semaine)
-
-
-
-
-
-
📥 Téléchargement automatique
-
Télécharger automatiquement les nouveaux épisodes
-
-
-
-
-
-
-
-
-
Maximum 5 téléchargements en parallèle
-
-
-
-
-
-
🔔 Notifications
-
Être notifié des nouveaux épisodes
-
-
-
-
-
-
-
- 💾 Enregistrer
-
-
- Annuler
-
-
-
-
-
-
- `;
-
+ const modalHtml = createSettingsModal(settings);
+
// Add modal to body
const modalContainer = document.createElement('div');
modalContainer.innerHTML = modalHtml;
document.body.appendChild(modalContainer);
-
} catch (error) {
console.error('Error loading settings:', error);
alert(`❌ Erreur: ${error.message}`);
}
}
- /**
- * Close settings modal
- */
- function closeSettingsModal() {
- const modal = document.getElementById('settingsModal');
- if (modal) {
- modal.remove();
- }
- }
-
- /**
- * Save settings
- */
- async function saveSettings() {
- try {
- const checkInterval = parseInt(document.getElementById('checkInterval').value);
- const autoDownloadEnabled = document.getElementById('autoDownloadEnabled').checked;
- const maxConcurrent = parseInt(document.getElementById('maxConcurrent').value);
- const notifyEnabled = document.getElementById('notifyEnabled').checked;
-
- const settings = {
- check_interval_hours: checkInterval,
- auto_download_enabled: autoDownloadEnabled,
- max_concurrent_auto_downloads: maxConcurrent,
- notify_on_new_episodes: notifyEnabled
- };
-
- await updateWatchlistSettings(settings);
-
- // Restart scheduler if it's running to apply new interval
- const status = await getSchedulerStatus();
- if (status.running) {
- await stopScheduler();
- await startScheduler();
- }
-
- closeSettingsModal();
- alert('✅ Paramètres enregistrés avec succès!');
- await loadSchedulerStatus();
-
- } catch (error) {
- console.error('Error saving settings:', error);
- alert(`❌ Erreur: ${error.message}`);
- }
- }
-
// Auto-refresh scheduler status every 30 seconds
setInterval(loadSchedulerStatus, 30000);
-
+