feat: Improve Anime-Sama search with fuzzy matching

Replace direct URL conversion with official Anime-Sama search API that
handles typos, partial matches, and returns multiple results with cover images.

Changes:
- Use /template-php/defaut/fetch.php API endpoint for search
- Parse HTML search results to extract title, URL, and cover image
- Return multiple results instead of single direct match
- Support for fuzzy matching (e.g., "hell paradise" finds "Hell's Paradise")

Examples:
- "hell paradise" → finds "Hell's Paradise" (handles missing 's')
- "one pie" → finds "One Piece" (handles incomplete words)
- "dragon" → returns 5 Dragon Ball series with cover images

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:
root
2026-01-23 08:28:15 +00:00
parent cb3ea8d926
commit c977306020
+42 -16
View File
@@ -350,42 +350,68 @@ class AnimeSamaDownloader(BaseDownloader):
"""
Search for anime on anime-sama
Returns list of anime with title, url, and cover image
Uses the official Anime-Sama search API which handles typos and fuzzy matching
"""
try:
# Update domains before searching to ensure we have the current domain
await self.update_domains()
import time
from html import unescape
start = time.time()
print(f"[ANIME-SAMA] Searching for '{query}' ({lang})...")
# Use the current domain from anime-sama.pw
current_domain = await self.get_current_domain()
# Convert query to URL format (lowercase, replace spaces with hyphens)
query_formatted = query.lower().replace(' ', '-').replace("'", '').replace(':', '')
search_url = f"https://{current_domain}/catalogue/{query_formatted}/saison1/{lang}/"
# Use the official search API endpoint
search_api_url = f"https://{current_domain}/template-php/defaut/fetch.php"
response = await self.client.get(search_url, follow_redirects=True)
# Make POST request to search API
response = await self.client.post(
search_api_url,
data={'query': query},
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
elapsed = time.time() - start
print(f"[ANIME-SAMA] Got response {response.status_code} in {elapsed:.2f}s")
print(f"[ANIME-SAMA] Got search response in {elapsed:.2f}s")
if response.status_code == 200:
# Check if it's a valid anime page by looking for episode selector
if 'selectEpisodes' in response.text or 'episodes.js' in response.text:
print(f"[ANIME-SAMA] Found anime at {str(response.url)}")
return [{
'title': query,
'url': str(response.url),
'type': 'direct'
}]
if response.status_code == 200 and response.text.strip():
# Parse HTML results
soup = BeautifulSoup(response.text, 'lxml')
results = []
print(f"[ANIME-SAMA] No anime found (status: {response.status_code})")
# Extract all search result links
for link in soup.find_all('a', class_='asn-search-result'):
href = link.get('href', '')
title_elem = link.find('h3', class_='asn-search-result-title')
img_elem = link.find('img', class_='asn-search-result-img')
title = unescape(title_elem.get_text()) if title_elem else "Unknown"
cover_image = img_elem.get('src', '') if img_elem else None
# Add language parameter to URL
if '/saison1/' not in href:
href = href.rstrip('/') + f'/saison1/{lang}/'
results.append({
'title': title,
'url': href,
'cover_image': cover_image,
'type': 'search_result'
})
print(f"[ANIME-SAMA] Found {len(results)} results")
return results
print(f"[ANIME-SAMA] No results found")
return []
except Exception as e:
print(f"[ANIME-SAMA] Error: {str(e)}")
print(f"[ANIME-SAMA] Search error: {str(e)}")
import traceback
traceback.print_exc()
return []
async def get_episodes(self, anime_url: str, lang: str = "vostfr") -> list[dict]: