@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.
- package/README.md +176 -0
- package/package.json +36 -0
- 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
|
+
}
|