d4d8d8a3b6
- Migrated monolithic main.py to feature-scoped routers in app/routers/ - Added GEMINI.md for project context and AI instructional guidelines - Updated README.md with a comprehensive modernization plan (SQL migration, robust scraping DSL, frontend modernization) - Improved authentication with cookie support and modular JS - Updated test suite and documentation
461 lines
11 KiB
JavaScript
461 lines
11 KiB
JavaScript
/**
|
|
* Watchlist management and auto-download UI
|
|
* Note: API_BASE is defined in api.js (loaded before this file)
|
|
*/
|
|
|
|
/**
|
|
* Get user's watchlist
|
|
*/
|
|
async function getWatchlist(status = null) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
let url = `${API_BASE}/watchlist`;
|
|
if (status) {
|
|
url += `?status=${status}`;
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch watchlist');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Add anime to watchlist
|
|
*/
|
|
async function addToWatchlist(animeData) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(animeData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Failed to add to watchlist');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Update watchlist item
|
|
*/
|
|
async function updateWatchlistItem(itemId, updateData) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/${itemId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(updateData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update watchlist item');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Delete from watchlist
|
|
*/
|
|
async function deleteFromWatchlist(itemId) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/${itemId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete from watchlist');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Pause watchlist item
|
|
*/
|
|
async function pauseWatchlistItem(itemId) {
|
|
return await updateWatchlistItem(itemId, { status: 'paused' });
|
|
}
|
|
|
|
/**
|
|
* Resume watchlist item
|
|
*/
|
|
async function resumeWatchlistItem(itemId) {
|
|
return await updateWatchlistItem(itemId, { status: 'active' });
|
|
}
|
|
|
|
/**
|
|
* Check specific anime for new episodes
|
|
*/
|
|
async function checkWatchlistItem(itemId) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/${itemId}/check`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to check for new episodes');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Check all watchlist items
|
|
*/
|
|
async function checkAllWatchlistItems() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/check-all`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to check all items');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Get watchlist settings
|
|
*/
|
|
async function getWatchlistSettings() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/settings`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch settings');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Update watchlist settings
|
|
*/
|
|
async function updateWatchlistSettings(settings) {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/settings`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(settings)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to update settings');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Get watchlist statistics
|
|
*/
|
|
async function getWatchlistStats() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/stats`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch statistics');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Get scheduler status
|
|
*/
|
|
async function getSchedulerStatus() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/scheduler/status`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch scheduler status');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Start scheduler
|
|
*/
|
|
async function startScheduler() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/scheduler/start`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to start scheduler');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
/**
|
|
* Stop scheduler
|
|
*/
|
|
async function stopScheduler() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
throw new Error('Not authenticated');
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/watchlist/scheduler/stop`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to stop scheduler');
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
// Make functions available globally
|
|
window.getWatchlist = getWatchlist;
|
|
window.addToWatchlist = addToWatchlist;
|
|
window.updateWatchlistItem = updateWatchlistItem;
|
|
window.deleteFromWatchlist = deleteFromWatchlist;
|
|
window.pauseWatchlistItem = pauseWatchlistItem;
|
|
window.resumeWatchlistItem = resumeWatchlistItem;
|
|
window.checkWatchlistItem = checkWatchlistItem;
|
|
window.checkAllWatchlistItems = checkAllWatchlistItems;
|
|
window.getWatchlistSettings = getWatchlistSettings;
|
|
window.updateWatchlistSettings = updateWatchlistSettings;
|
|
window.getWatchlistStats = getWatchlistStats;
|
|
window.getSchedulerStatus = getSchedulerStatus;
|
|
window.startScheduler = startScheduler;
|
|
window.stopScheduler = stopScheduler;
|
|
|
|
|
|
/**
|
|
* Current filter state
|
|
*/
|
|
let currentFilter = 'all';
|
|
|
|
/**
|
|
* Filter watchlist
|
|
*/
|
|
async function filterWatchlist(status, tabElement) {
|
|
currentFilter = status;
|
|
|
|
// Update tab styles
|
|
document.querySelectorAll('.filter-tab').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
tabElement.classList.add('active');
|
|
|
|
// Reload with filter
|
|
await displayWatchlist(status === 'all' ? null : status);
|
|
}
|
|
|
|
/**
|
|
* Handle start scheduler
|
|
*/
|
|
async function handleStartScheduler() {
|
|
try {
|
|
await startScheduler();
|
|
await loadSchedulerStatus();
|
|
alert('✅ Planificateur démarré!');
|
|
} catch (error) {
|
|
console.error('Error starting scheduler:', error);
|
|
alert(`❌ Erreur: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle stop scheduler
|
|
*/
|
|
async function handleStopScheduler() {
|
|
try {
|
|
await stopScheduler();
|
|
await loadSchedulerStatus();
|
|
alert('✅ Planificateur arrêté!');
|
|
} catch (error) {
|
|
console.error('Error stopping scheduler:', error);
|
|
alert(`❌ Erreur: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle check all
|
|
*/
|
|
async function handleCheckAll() {
|
|
try {
|
|
await checkAllWatchlistItems();
|
|
await loadSchedulerStatus();
|
|
} catch (error) {
|
|
console.error('Error checking all:', error);
|
|
alert(`❌ Erreur: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle open settings
|
|
*/
|
|
async function handleOpenSettings() {
|
|
try {
|
|
const settings = await getWatchlistSettings();
|
|
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}`);
|
|
}
|
|
}
|
|
|
|
// Make functions available globally
|
|
window.filterWatchlist = filterWatchlist;
|
|
window.handleStartScheduler = handleStartScheduler;
|
|
window.handleStopScheduler = handleStopScheduler;
|
|
window.handleCheckAll = handleCheckAll;
|
|
window.handleOpenSettings = handleOpenSettings;
|
|
|
|
/**
|
|
* Load scheduler status
|
|
*/
|
|
async function loadSchedulerStatus() {
|
|
try {
|
|
const status = await getSchedulerStatus();
|
|
updateSchedulerUI(status);
|
|
} catch (error) {
|
|
console.error('Error loading scheduler status:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update scheduler UI
|
|
*/
|
|
function updateSchedulerUI(status) {
|
|
const startBtn = document.getElementById('startSchedulerBtn');
|
|
const stopBtn = document.getElementById('stopSchedulerBtn');
|
|
const nextRunInfo = document.getElementById('nextRunInfo');
|
|
|
|
// nextRunInfo is required, but buttons are optional
|
|
if (!nextRunInfo) {
|
|
console.warn('nextRunInfo element not found');
|
|
return;
|
|
}
|
|
|
|
if (status.running) {
|
|
// Update buttons if they exist
|
|
if (startBtn) startBtn.style.display = 'none';
|
|
if (stopBtn) stopBtn.style.display = 'inline-block';
|
|
|
|
if (status.next_run) {
|
|
const nextRun = new Date(status.next_run);
|
|
nextRunInfo.innerHTML = `✓ En cours<br>Prochaine vérification: ${nextRun.toLocaleString('fr-FR')}`;
|
|
} else {
|
|
// Scheduler running but no next_run yet (just started)
|
|
const interval = status.settings?.check_interval_hours || 6;
|
|
nextRunInfo.innerHTML = `✓ En cours<br>Vérification toutes les ${interval}h`;
|
|
}
|
|
} else {
|
|
// Update buttons if they exist
|
|
if (startBtn) startBtn.style.display = 'inline-block';
|
|
if (stopBtn) stopBtn.style.display = 'none';
|
|
nextRunInfo.innerHTML = '⏸️ Arrêté';
|
|
}
|
|
}
|
|
|
|
window.loadSchedulerStatus = loadSchedulerStatus;
|
|
window.updateSchedulerUI = updateSchedulerUI;
|
|
window.filterWatchlist = filterWatchlist;
|
|
window.handleStartScheduler = handleStartScheduler;
|
|
window.handleStopScheduler = handleStopScheduler;
|
|
window.handleCheckAll = handleCheckAll;
|
|
window.handleOpenSettings = handleOpenSettings; |