MangaReader

Un lecteur de manga qui scrape des chapitres depuis des sites tiers, les fusionne en séries, et les sert via une interface web.

Ce que ça fait

MangaReader scrape des chapitres de manga depuis des sites de scanlation, les fusionne en séries, et les sert via une interface web locale. Tu peux marquer tes séries, suivre ta progression, et le serveur synchronise les nouveaux chapitres en arrière-plan.

Architecture

Trois crates Rust dans un workspace :

  • mangaread-core — types du domaine, repository SQLite, logique de fusion des séries
  • scraper — trait SiteAdapter avec implémentations par site (Demonicscans, Manhuaplus), binaire CLI + bibliothèque
  • website — serveur web Axum avec 10 modules de routes, templates Maud, assets statiques

La base de données utilise SQLite en mode WAL — quatre tables (series, manga, chapters, pages), tous les inserts utilisent ON CONFLICT ... DO UPDATE, donc tout est sûr à relancer.

flowchart TB
    CLI[ScraperEngine] --> DB[(SQLite)]
    WEB[Axum Server] --> DB
    WEB <--> BROWSER[Browser]
    BROWSER --> JS[app.js + localStorage]

État côté client sans comptes. Pas d'auth, pas de sessions. Les marque-pages et la progression vivent dans localStorage. Le JS lit localStorage et construit le DOM avec une petite fonction h(). C'est étonnamment propre — mais debugguer l'état sans base de données, c'est plus dur.

Captures d'écran

Page d'accueil MangaReader
Accueil — Parcourir votre bibliothèque
Vue série MangaReader
Série — Voir les chapitres
Détails série MangaReader
Détails — Gérer et organiser
Lecture chapitre MangaReader
Lecture — Interface de lecture

Les parties difficiles

Chromedriver. Le scraper utilise un vrai navigateur pour les sites manga chargés en JS. Faire marcher chromedriver dans Docker a pris 30% du temps de dev. Bugs de mode headless, problèmes de sandbox, processus zombies — le cirque habituel des webdrivers.

SQLite et le threading. La Connection de rusqlite n'est pas Sync, donc chaque thread de scraping ouvre sa propre connexion. Le AppState principal wrappe la sienne dans un Mutex. Ça semble simple, mais le borrow checker avait des opinions.

Se battre avec le borrow checker pour la 50ème fois à 2h du matin, c'est un rite de passage.

Ce que j'ai appris

C'était mon premier projet Rust avec plusieurs crates dans un workspace, et ça m'a plus appris que n'importe quel tutoriel. Le système de types de Rust est génial pour la modélisation du domaine — pas d'états invalides possibles — mais se battre avec le borrow checker pour la 50ème fois à 2h du matin, c'est un rite de passage.

Le pattern d'adaptateur — trait + implémentations par site — a payé immédiatement en ajoutant une deuxième source. Et la boucle de sync (thread background, intervalle de 6h, scraping parallèle) m'a appris les channels Tokio et l'arrêt gracieux à la dure.