Files
ohm_streaming/static/js/api.js
T
root 1fe7392063 feat: Complete Sonarr integration with security enhancements
This commit adds comprehensive Sonarr webhook integration and implements
critical security improvements identified in code review.

## Sonarr Integration
- Full webhook support for Grab, Download, Rename, Delete, and Test events
- HMAC SHA256 signature verification for webhook authentication
- Series mapping system (Sonarr TVDB ID → Anime Provider URL)
- 11 new API endpoints for configuration, mappings, search, and downloads
- Comprehensive test suite (31 tests, all passing)
- Complete documentation in docs/SONARR_INTEGRATION.md

## Security Enhancements
- CORS restricted to specific origins (user's IP: 192.168.1.204:3000)
- Path traversal prevention via sanitize_filename() and is_safe_filename()
- Structured logging infrastructure (replaced all print() statements)
- Environment-based configuration with .env support
- Filename sanitization prevents malicious path attacks

## New Features
- Lpayer and Sibnet downloader support
- Kitsu API integration for anime metadata
- Recommendation engine based on download history
- Latest releases endpoint for new anime
- Modular web interface with component-based templates

## Configuration
- Centralized settings via app/config.py with pydantic-settings
- Sonarr config auto-created in config/ directory
- Example configurations provided for easy setup

## Tests
- 31 Sonarr integration tests (23 functionality + 9 security)
- 100+ tests passing in core test files
- Security utilities fully tested

## Documentation
- Updated CLAUDE.md with Sonarr and testing info
- Added IMPROVEMENTS_2024-01-24.md analysis
- Added SONARR_IMPLEMENTATION.md technical summary

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
2026-01-24 21:25:47 +00:00

152 lines
3.5 KiB
JavaScript

// API Base configuration
const API_BASE = '/api';
// Cache for providers info
let searchResultsCache = {};
/**
* Get providers information
*/
async function getProvidersInfo() {
if (!searchResultsCache.providers) {
const response = await fetch(`${API_BASE}/providers`);
searchResultsCache.providers = await response.json();
}
return searchResultsCache.providers;
}
/**
* Search anime across all providers
*/
async function searchAnime(query, lang, includeMetadata) {
if (!query) {
throw new Error('Veuillez entrer un nom d\'anime');
}
const response = await fetch(
`${API_BASE}/anime/search?q=${encodeURIComponent(query)}&lang=${lang}&include_metadata=${includeMetadata}`
);
if (!response.ok) {
throw new Error('Erreur lors de la recherche');
}
return await response.json();
}
/**
* Load episodes for an anime
*/
async function loadEpisodes(animeUrl, lang) {
const response = await fetch(
`${API_BASE}/anime/episodes?url=${encodeURIComponent(animeUrl)}&lang=${lang}`
);
if (!response.ok) {
throw new Error('Erreur lors du chargement des épisodes');
}
return await response.json();
}
/**
* Download an anime episode
*/
async function downloadEpisode(episodeUrl) {
const response = await fetch(`${API_BASE}/anime/download?url=${encodeURIComponent(episodeUrl)}`, {
method: 'POST'
});
if (!response.ok) {
throw new Error('Erreur lors du démarrage du téléchargement');
}
return await response.json();
}
/**
* Download entire season
*/
async function downloadSeason(animeUrl, lang) {
const response = await fetch(
`${API_BASE}/anime/download-season?url=${encodeURIComponent(animeUrl)}&lang=${lang}`,
{ method: 'POST' }
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Impossible de démarrer le téléchargement de la saison');
}
return await response.json();
}
/**
* Start a direct download
*/
async function startDownload(url) {
const response = await fetch(`${API_BASE}/download`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
});
if (!response.ok) {
throw new Error('Erreur lors du démarrage du téléchargement');
}
return await response.json();
}
/**
* Get all downloads
*/
async function getDownloads() {
const response = await fetch(`${API_BASE}/downloads`);
if (!response.ok) {
throw new Error('Erreur lors du chargement des téléchargements');
}
return await response.json();
}
/**
* Pause a download
*/
async function pauseDownload(id) {
const response = await fetch(`${API_BASE}/download/${id}/pause`, { method: 'POST' });
if (!response.ok) {
throw new Error('Erreur lors de la mise en pause');
}
return await response.json();
}
/**
* Resume a download
*/
async function resumeDownload(id) {
const response = await fetch(`${API_BASE}/download/${id}/resume`, { method: 'POST' });
if (!response.ok) {
throw new Error('Erreur lors de la reprise');
}
return await response.json();
}
/**
* Cancel/delete a download
*/
async function cancelDownload(id) {
const response = await fetch(`${API_BASE}/download/${id}`, { method: 'DELETE' });
if (!response.ok) {
throw new Error('Erreur lors de la suppression');
}
return await response.json();
}