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 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
{# Template pour un onglet de provider anime spécifique #}
|
||||
{# Variables disponibles: provider_id, provider_info #}
|
||||
<div id="tab-anime-{{ provider_id }}" class="tab-content">
|
||||
<div class="url-form">
|
||||
<div class="anime-input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="searchInput-{{ provider_id }}"
|
||||
placeholder="Rechercher un anime sur {{ provider_info.name }}..."
|
||||
onkeypress="if(event.key === 'Enter') searchAnimeProvider('{{ provider_id }}')"
|
||||
>
|
||||
<select id="langSelect-{{ provider_id }}" style="max-width: 120px;">
|
||||
<option value="vostfr">VOSTFR</option>
|
||||
<option value="vf">VF</option>
|
||||
</select>
|
||||
<button type="button" class="btn-primary" onclick="searchAnimeProvider('{{ provider_id }}')">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Rechercher
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 10px; margin-top: 10px; font-size: 13px; color: #888;">
|
||||
<input type="checkbox" id="includeMetadata-{{ provider_id }}" style="width: auto; margin: 0;">
|
||||
<label for="includeMetadata-{{ provider_id }}" style="cursor: pointer; user-select: none;">
|
||||
📊 Inclure les métadonnées
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="searchResults-{{ provider_id }}" class="search-results"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<!-- Direct Download Tab -->
|
||||
<div id="tab-direct" class="tab-content">
|
||||
<div class="url-form">
|
||||
<form id="downloadForm">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="urlInput"
|
||||
placeholder="Collez le lien de téléchargement ici..."
|
||||
required
|
||||
>
|
||||
<button type="submit" class="btn-primary">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Télécharger
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="supported-hosts">
|
||||
<span class="host-badge">1fichier</span>
|
||||
<span class="host-badge">Doodstream</span>
|
||||
<span class="host-badge">Rapidfile</span>
|
||||
<span class="host-badge">Anime-Sama</span>
|
||||
<span class="host-badge">Anime-Ultime</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!-- Downloads Section with Filters -->
|
||||
<div class="section-header">
|
||||
<h2>Téléchargements</h2>
|
||||
<div class="downloads-stats" id="downloadsStats"></div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Controls -->
|
||||
<div class="downloads-controls">
|
||||
<div class="filter-group">
|
||||
<label>Statut:</label>
|
||||
<select id="statusFilter" onchange="filterDownloads()">
|
||||
<option value="all">Tous</option>
|
||||
<option value="downloading">En cours</option>
|
||||
<option value="paused">En pause</option>
|
||||
<option value="completed">Terminés</option>
|
||||
<option value="cancelled">Annulés</option>
|
||||
<option value="failed">Échoués</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Tri par:</label>
|
||||
<select id="sortBy" onchange="filterDownloads()">
|
||||
<option value="date">Date (récent)</option>
|
||||
<option value="date_asc">Date (ancien)</option>
|
||||
<option value="name">Nom (A-Z)</option>
|
||||
<option value="name_desc">Nom (Z-A)</option>
|
||||
<option value="size">Taille</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Regroupement:</label>
|
||||
<select id="groupBy" onchange="filterDownloads()">
|
||||
<option value="none">Aucun</option>
|
||||
<option value="series">Par série</option>
|
||||
<option value="status">Par statut</option>
|
||||
<option value="day">Par jour</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-group search-group">
|
||||
<input type="text" id="searchDownloads" placeholder="🔍 Rechercher..." oninput="filterDownloads()">
|
||||
</div>
|
||||
|
||||
<div class="actions-group">
|
||||
<button class="btn-small btn-secondary" onclick="clearCompleted()" title="Supprimer annulés, échoués et terminés">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:14px;height:14px;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
Nettoyer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="downloadsList" class="downloads-list">
|
||||
<div class="empty-state">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"></path>
|
||||
</svg>
|
||||
<p>Aucun téléchargement pour le moment</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<h1>⚡ Ohm Stream Downloader</h1>
|
||||
<p class="subtitle">Téléchargez vos vidéos et animes depuis vos hébergeurs préférés</p>
|
||||
|
||||
<!-- Tabs (Anime providers will be added dynamically) -->
|
||||
<div class="tabs">
|
||||
<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="search" onclick="switchTab('search')">
|
||||
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Recherche
|
||||
</button>
|
||||
<button class="tab" data-tab-type="direct" onclick="switchTab('direct')">
|
||||
<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="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
|
||||
</svg>
|
||||
Lien direct
|
||||
</button>
|
||||
<!-- Anime provider tabs will be loaded dynamically -->
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!-- Home Section: Recommendations & Latest Releases -->
|
||||
<div id="tab-home" class="tab-content active">
|
||||
<!-- Loading State -->
|
||||
<div id="homeLoading" class="loading-spinner">Chargement des recommandations...</div>
|
||||
|
||||
<!-- Recommendations Section -->
|
||||
<div id="recommendationsSection" style="display: none;">
|
||||
<div class="section-header">
|
||||
<h2>🎯 Recommandé pour vous</h2>
|
||||
<button class="btn-small btn-secondary" onclick="loadRecommendations()">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:14px;height:14px;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Actualiser
|
||||
</button>
|
||||
</div>
|
||||
<div id="recommendationsList" class="search-results"></div>
|
||||
</div>
|
||||
|
||||
<!-- Latest Releases Section -->
|
||||
<div id="releasesSection" style="display: none; margin-top: 40px;">
|
||||
<div class="section-header">
|
||||
<h2>🔥 Dernières sorties de la saison</h2>
|
||||
<button class="btn-small btn-secondary" onclick="loadLatestReleases()">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:14px;height:14px;">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Actualiser
|
||||
</button>
|
||||
</div>
|
||||
<div id="releasesList" class="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!-- Search Tab -->
|
||||
<div id="tab-search" class="tab-content">
|
||||
<div class="url-form">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="searchInput"
|
||||
placeholder="Rechercher un anime (ex: Frieren, One Piece...)"
|
||||
>
|
||||
<button type="button" class="btn-primary" onclick="handleSearch()">
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Rechercher
|
||||
</button>
|
||||
</div>
|
||||
<div style="margin-top: 10px; padding: 10px; background: rgba(0, 217, 255, 0.1); border-radius: 8px; font-size: 12px; color: #aaa;">
|
||||
💡 <strong>Info:</strong> La recherche utilise MyAnimeList pour afficher la fiche complète de l'anime (synopsis, saisons, etc.) et trouve les sources de streaming disponibles.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Anime details and streaming results -->
|
||||
<div id="animeSearchResults"></div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user