Phase 3: HTMX & Alpine.js integration, router refactoring, and UI modernization
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled

- Modernized the frontend with HTMX for server-driven UI and Alpine.js for client state.
- Refactored anime, player, and recommendation logic into modular routers.
- Updated README.md to reflect the latest project state and technologies (v2.4).
- Added Plyr.io for an improved streaming experience.
- Improved project structure with componentized templates.
- Added Playwright and Vitest configuration for frontend testing.
This commit is contained in:
root
2026-03-26 10:34:26 +00:00
parent a684237725
commit 9f85908ff3
31 changed files with 3413 additions and 2201 deletions
+52 -24
View File
@@ -68,38 +68,66 @@ class FS7Downloader(BaseSeriesSite):
soup = BeautifulSoup(html, 'lxml')
results = []
# Look for series items (FS7 has both films and series in search results)
# We filter for /s-tv/ URLs ending with .html (actual series/season pages)
items = soup.find_all('a', href=re.compile(r'/s-tv/\d+-.+\.html'))
# Look for series items
# FS7 usually structure: <div class="movie-item">...<a href="..."><img src="..."></a>...</div>
# Or directly <a> tags with images
items = soup.find_all('div', class_='movie-item')
if not items:
# Fallback to the previous method if layout is different
items = soup.find_all('a', href=re.compile(r'/s-tv/\d+-.+\.html'))
for item in items[:20]: # Limit to 20 results
url = item.get('href', '')
for item in items[:24]: # Limit to 24 results
# Find the link and image within the item or the item itself
if item.name == 'a':
link_elem = item
else:
link_elem = item.find('a', href=re.compile(r'/s-tv/|/films/'))
if not link_elem:
continue
url = link_elem.get('href', '')
if not url.startswith('http'):
url = urljoin(self.base_url, url)
# Extract title from the item
title_elem = item.find('img', alt=True)
if title_elem:
title = title_elem.get('alt', '').strip()
# Extract title
img_elem = item.find('img')
title = ""
if img_elem and img_elem.get('alt'):
title = img_elem.get('alt').strip()
elif link_elem.get('title'):
title = link_elem.get('title').strip()
else:
# Get text content and clean it
text = item.get_text(strip=True)
# Skip if it's just a category name
if any(cat in text.lower() for cat in ['séries', 'series', 'vf', 'vostfr', 'vo', 'netflix', 'disney', 'amazon', 'apple']):
continue
title = text
# Clean up title: remove "affiche" suffix and clean extra whitespace
title = re.sub(r'\s+affiche$', '', title, flags=re.IGNORECASE).strip()
title = re.sub(r'\s+', ' ', title) # Normalize whitespace
title = item.get_text(strip=True)
# Extract cover image
img = item.find('img')
cover_image = img.get('src', '') if img else ''
img_elem = item.find('img')
cover_image = ""
if img_elem:
# Check for common lazy loading attributes used by various themes
cover_image = (
img_elem.get('data-src') or
img_elem.get('data-original') or
img_elem.get('src') or
""
)
# If still empty, look for background-style images in inline styles
if not cover_image:
style = item.get('style', '')
if 'background-image' in style:
match = re.search(r'url\([\'"]?(.*?)[\'"]?\)', style)
if match:
cover_image = match.group(1)
# Only add if we have a title and it's not empty
if title and len(title) > 5:
# Avoid duplicates
if cover_image and not cover_image.startswith('http'):
cover_image = urljoin(self.base_url, cover_image)
# Clean up title
title = re.sub(r'\s+affiche$', '', title, flags=re.IGNORECASE).strip()
title = re.sub(r'\s+', ' ', title)
if title and len(title) > 2:
if not any(r['url'] == url for r in results):
results.append({
'title': title,