@aphrody/animesama 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +176 -0
  2. package/package.json +36 -0
  3. package/src/index.ts +1202 -0
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # @aphrody/animesama
2
+
3
+ Scraper typé pour **[anime-sama.to](https://anime-sama.to)** : catalogue,
4
+ saisons, langues, épisodes et résolution des lecteurs vers un flux direct.
5
+
6
+ Extraction **purement textuelle** — pas de DOM, pas d'exécution de JS. Le module
7
+ analyse le HTML rendu côté serveur et les fichiers `episodes.js`, donc il
8
+ fonctionne aussi bien sur un miroir persisté que sur une réponse live. Le
9
+ transport HTTP est **injectable**, ce qui rend l'ensemble testable sans réseau.
10
+
11
+ - Code : `packages/animesama/src/index.ts`
12
+ - Tests (sans réseau) : `packages/animesama/src/index.test.ts`
13
+ - Fixtures réelles : `packages/animesama/test/fixtures/`
14
+
15
+ ## Anatomie du site (rétro-ingénierie du 2026-09-03)
16
+
17
+ | Ressource | Chemin | Contenu |
18
+ | --- | --- | --- |
19
+ | Fiche d'une œuvre | `/catalogue/<slug>/` | titre, titres alternatifs, synopsis, genres, état, année, studios — et les saisons **en JavaScript** |
20
+ | Page de saison | `/catalogue/<slug>/<saison>/<langue>/` | titre, sélecteurs, et le script qui compose les libellés d'épisodes |
21
+ | Lecteurs | `/catalogue/<slug>/<saison>/<langue>/episodes.js` | une variable `epsN` par lecteur, une case par épisode |
22
+ | Recherche instantanée | `POST /template-php/defaut/fetch.php` (`query=<texte>`) | fragment de `<a class="asn-search-result">` |
23
+ | Catalogue paginé | `GET /catalogue/?search=<texte>&page=N` | cartes `.catalog-card` |
24
+
25
+ Quatre pièges que le module absorbe :
26
+
27
+ 1. **Les saisons ne sont pas du HTML.** Elles sont écrites par des appels
28
+ `panneauAnime("Saison 1", "saison1/vf")` (et `panneauScan(…)` pour les scans)
29
+ exécutés via `document.write`. C'est la seule source de vérité.
30
+ 2. **`episodes.js` n'a pas de forme canonique.** On croise `var eps2` déclaré
31
+ avant `var eps1`, `eps1` absent, une déclaration entière sur une seule ligne,
32
+ des virgules traînantes. `videos.js` **échange** ensuite `eps1` et `eps2` à
33
+ l'affichage ; ce module conserve la numérotation brute du fichier
34
+ (`Lecteur.index`) et nomme les lecteurs dans l'ordre croissant (`Lecteur.nom`).
35
+ 3. **Les libellés d'épisodes sont calculés.** La page appelle `resetListe()`,
36
+ `creerListe(debut, fin)`, `newSP(n)`, `newSPF("nom libre")`, `finirListe(debut)`.
37
+ Le dernier `resetListe()` gagne : le bloc par défaut est souvent suivi d'un
38
+ second bloc (films). Un gabarit **commenté** contient les mêmes appels — les
39
+ commentaires sont retirés avant analyse, sans casser les `//` des URLs.
40
+ 4. **Les drapeaux de langue ne disent rien.** Le gabarit imprime les dix
41
+ drapeaux, tous `hidden` ; c'est `videos.js` qui sonde `../<langue>` en HTTP
42
+ pour révéler ceux qui existent. Seul `listerLangues()` dit la vérité.
43
+
44
+ ### Hébergeurs rencontrés
45
+
46
+ `ansembed.net`, `lpayer.embed4me.com`, `video.sibnet.ru`, `sendvid.com`,
47
+ `movearnpre.com`, `oneupload.to`, `s22.anime-sama.fr` (mp4 direct),
48
+ `www.youtube.com/embed`, `www.dailymotion.com/embed`, `vidmoly`, `myvi.top`.
49
+
50
+ ## CLI
51
+
52
+ ```bash
53
+ bxc animesama search <requête> # recherche instantanée (POST fetch.php)
54
+ bxc animesama info <slug|url> # fiche + saisons déclarées
55
+ bxc animesama seasons <slug> # saisons + langues réellement publiées
56
+ bxc animesama episodes <slug> # épisodes d'une saison (--season/--lang)
57
+ bxc animesama resolve <url-embed> # embed → flux direct (+ variantes HLS)
58
+ ```
59
+
60
+ Options : `--season <dossier>` (défaut `saison1`, accepte `film`, `oav`…),
61
+ `--lang <code>` (`vostfr` par défaut ; `vf`, `va`, `var`, `vkr`, `vcn`, `vqc`,
62
+ `vf1`, `vf2`), `--profile static|fast|http|stealth|max` (défaut `static`),
63
+ `--timeout <ms>`.
64
+
65
+ ```bash
66
+ $ bxc animesama search inazuma
67
+ [{ "slug": "inazuma-eleven", "titre": "Inazuma Eleven", … }]
68
+
69
+ $ bxc animesama seasons inazuma-eleven
70
+ { "slug": "inazuma-eleven", "saisons": [{ "saison": "saison1", "noms": ["Saison 1"], "langues": ["vf"] }, …] }
71
+
72
+ $ bxc animesama episodes inazuma-eleven --season saison1 --lang vf
73
+ { "titre": "Inazuma Eleven", "libelle": "Saison 1", "lecteurs": [ … ], "episodes": [ … ] }
74
+
75
+ $ bxc animesama resolve "https://video.sibnet.ru/shell.php?videoid=4826196"
76
+ { "hebergeur": "sibnet", "type": "mp4", "url": "https://video.sibnet.ru/v/…/4826196.mp4" }
77
+ ```
78
+
79
+ ## API
80
+
81
+ ```ts
82
+ import { AnimesamaScraper } from "@aphrody/animesama";
83
+
84
+ const as = new AnimesamaScraper(); // profile "static" par défaut
85
+
86
+ const resultats = await as.rechercher("inazuma");
87
+ const fiche = await as.getAnime("inazuma-eleven");
88
+ console.log(fiche.titre, fiche.saisons.map((s) => s.nom));
89
+
90
+ const langues = await as.listerLangues("inazuma-eleven", "saison1"); // ["vf"]
91
+
92
+ const saison = await as.getSaison("inazuma-eleven", "saison1", "vf");
93
+ console.log(saison.episodes.length); // 26
94
+ console.log(saison.episodes[0].lecteurs); // [{ hebergeur: "youtube", … }, …]
95
+
96
+ const source = await as.resoudreLecteur(saison.episodes[0].lecteurs[1], {
97
+ enumererQualites: true,
98
+ });
99
+ console.log(source.type, source.url, source.enTetes.Referer);
100
+
101
+ await as.close();
102
+ ```
103
+
104
+ ### Surface exportée
105
+
106
+ | Symbole | Rôle |
107
+ | --- | --- |
108
+ | `AnimesamaScraper` | client haut niveau (défaut du module) |
109
+ | `AnimesamaOptions` | `profile`, `baseUrl`, `timeoutMs`, `retries`, `transport` |
110
+ | `rechercher` / `parcourirCatalogue` | recherche instantanée / catalogue paginé |
111
+ | `getAnime` / `getAnimeComplet` | fiche seule / fiche + toutes ses saisons |
112
+ | `getSaison` / `getEpisodesJs` / `listerLangues` | une saison, son `episodes.js` brut, ses langues sondées |
113
+ | `resoudreLecteur` / `enumererQualitesHls` | embed → flux direct, variantes d'un master HLS |
114
+ | `parserFicheAnime`, `parserSaisonsDeclarees`, `parserLecteurs`, `parserNomsEpisodes`, `parserSaison`, `parserResultatsRecherche`, `parserCartesCatalogue`, `parserDrapeauxLangues`, `composerEpisodes` | analyseurs **purs** (chaîne → objets), utilisables sur un miroir |
115
+ | `hebergeurDepuisUrl`, `normaliserUrlLecteur`, `chercherMedia`, `classerMedia`, `deballerPacker`, `numeroDepuisNom`, `estLangue`, `texteBrut`, `retirerCommentairesJs` | utilitaires |
116
+ | `FicheAnime`, `SaisonRef`, `SaisonAnimesama`, `EpisodeAnimesama`, `Lecteur`, `LecteurEpisode`, `SourceResolue`, `QualiteMedia`, `ResultatRecherche`, `LangueAnimesama`, `LANGUES_ANIMESAMA` | types publics |
117
+
118
+ ### Transport injectable
119
+
120
+ `AnimesamaScraper` n'ouvre une page bxc que si aucun `transport` n'est fourni.
121
+ En injecter un permet de brancher un cache, un miroir — ou des fixtures :
122
+
123
+ ```ts
124
+ const scraper = new AnimesamaScraper({
125
+ transport: async ({ url }) => ({ status: 200, corps: htmlDeFixture[url] }),
126
+ });
127
+ ```
128
+
129
+ Le transport par défaut (`creerTransportBxc`) fait les `GET` via une page bxc
130
+ (`profile`, `static` = zéro spawn) et le seul `POST` — la recherche — via `fetch`,
131
+ puisque `page.goto()` n'envoie pas de corps.
132
+
133
+ ## Tests
134
+
135
+ ```bash
136
+ bun test packages/animesama # 47 cas, aucun accès réseau
137
+ ```
138
+
139
+ Les fixtures de `test/fixtures/` sont des extraits **réels** capturés sur
140
+ anime-sama.to (fiche Inazuma Eleven, `episodes.js` de la saison 1 VF, `episodes.js`
141
+ de One Piece saison 1 VOSTFR pour le cas `eps2` avant `eps1`, bloc de liste des
142
+ films de Dragon Ball Super, résultats de `fetch.php`, cartes de catalogue, embeds
143
+ ansembed et sibnet).
144
+
145
+ ## Limites connues
146
+
147
+ - **Cloudflare** est devant le site. Les requêtes simples passent aujourd'hui
148
+ avec un User-Agent de navigateur (`profile: "static"`) ; en cas de challenge,
149
+ escalader vers `--profile fast` ou `stealth`.
150
+ - **YouTube et Dailymotion** ne sont pas résolus : ce sont des lecteurs
151
+ propriétaires, pas des embeds de fichier. `resoudreLecteur` le signale sans
152
+ émettre de requête.
153
+ - **Les URLs résolues sont signées et éphémères** (jeton `t=` + `e=` chez
154
+ ansembed / embed4me) et exigent l'en-tête `Referer` renvoyé dans `enTetes`.
155
+ - Les hébergeurs fortement obfusqués (voe, mail.ru et clones) ne sont pas
156
+ couverts : ils demanderaient un rendu JS.
157
+ - **Le sondage des langues coûte une requête par langue et par saison**
158
+ (`listerLangues` : 9 langues × N saisons). Restreindre la liste quand on sait
159
+ déjà ce qu'on cherche.
160
+
161
+ ## Résolution des lecteurs
162
+
163
+ La reconnaissance de l'hébergeur, le déballage des scripts compressés,
164
+ l'extraction de la piste et la lecture des playlists HLS ne vivent plus dans ce
165
+ paquet : ils sont dans le **cœur média de bxc** (`@aphrody/bxc/media`), partagé
166
+ avec `@aphrody/voiranime`. Les mêmes hébergeurs servent les deux sites — un
167
+ domaine qui change ou une page qui bouge se corrige à un seul endroit.
168
+
169
+ Ce paquet ne garde que la traduction du résultat dans son vocabulaire
170
+ (`hebergeur`, `enTetes`, `qualites`) et la connaissance propre à anime-sama :
171
+ `episodes.js`, `panneauAnime`, la numérotation des lecteurs, les langues.
172
+
173
+ `resoudreLecteur` rend donc, en plus de l'URL : les en-têtes `Referer`/`Origin`
174
+ à rejouer, l'aperçu, les variantes du master HLS en **URL absolues** (deux
175
+ qualités de même hauteur sont départagées par leur débit), et une erreur
176
+ explicite quand le lecteur est propriétaire (YouTube, Dailymotion) ou obfusqué.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@aphrody/animesama",
3
+ "version": "0.2.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "main": "./src/index.ts",
9
+ "types": "./src/index.ts",
10
+ "exports": {
11
+ ".": "./src/index.ts"
12
+ },
13
+ "scripts": {
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "bun test src/"
16
+ },
17
+ "keywords": [
18
+ "anime-sama",
19
+ "anime",
20
+ "scraper",
21
+ "streaming",
22
+ "vostfr"
23
+ ],
24
+ "dependencies": {},
25
+ "files": [
26
+ "src/",
27
+ "!src/**/*.test.ts",
28
+ "!src/**/*.spec.ts",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "engines": {
33
+ "bun": ">=1.3.14"
34
+ },
35
+ "license": "Apache-2.0"
36
+ }