# Extractor Portfolio Audit (June 2026)

Audit of 82 extractors in `~/apk-analysis/scraper-engine/extractors/` vs nexstream-scraper's pipeline.

## Key Finding: Zero Overlap

nexstream-scraper exclusively queries torrent-indexing APIs (Torrentio/Comet/MediaFusion/Jackett/Zilean) which return **torrent info hashes**. The 82 scraper-engine extractors handle **direct embed-to-m3u8** resolution for video hosters. They are entirely complementary — adding extractors to nexstream-scraper would be net-new functionality, not overlap.

## Extractor Ranking by Porting Value

### HIGH Value

| # | Extractor | Why Valuable |
|---|-----------|--------------|
| 1 | **FilemoonExtractor** | ECDSA secp256r1 attestation → AES/GCM decrypt. Full 4-step challenge handshake. **Most sophisticated, no public equivalent.** |
| 2 | **CloseloadExtractor** | Smart brute-force engine: 5 string transforms × 5 byte transforms × double-base64 × modular arithmetic decryption loop. |
| 3 | **VidsrcNetExtractor** | 10 different decryption functions in one file (switch-case by player ID): ROT13, Caesar shift, hex-to-char XOR, Base64+char-shift, substring extraction. |
| 4 | **AesHelper** (util) | Portable OpenSSL-compatible AES with MD5 KDF (salt + password → key+IV). Needed by Rabbitstream, Vidsrc.to, Vidplay. |

### MEDIUM Value

| Extractor | Technique |
|-----------|-----------|
| Rabbitstream | JS obfuscation key extraction from player script switch-case analysis |
| VidsrcTo | RC4 two-stage token encoding + key rotation from GitHub JSON |
| VidGuard | Multi-stage sig decode: hex-xor → base64 → drop-last-5 → reverse → char-pair-swap |
| StreamWish | URL registry of 75+ rotating domains (pack as JSON list for any resolver) |
| MixDrop | Rotating domain regex pattern `^md[3bfyz][a-z0-9]*` |
| JsUnpacker (util) | P.A.C.K.E.R. eval deobfuscator (needed by 12+ extractors) |

### LOW Value

Simple regex scrapers (Goodstream, LuluVdo, Supervideo, Uqload) — pattern is trivial to re-implement.

## Filemoon ECDSA Attestation Flow (Full Details)

Built for Node.js using native `crypto` module:

```js
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
  namedCurve: 'P-256',
  publicKeyEncoding: { type: 'spki', format: 'der' },
  privateKeyEncoding: { type: 'pkcs8', format: 'der' },
});
```

1. **GET** `/api/videos/{id}/embed/details` → `embed_frame_url`
2. **POST** `/api/videos/access/challenge` → `{ challenge_id, nonce }`
3. **Generate ECDSA P-256 keypair**, extract x/y from SPKI DER
4. **Sign** nonce with `SHA256withECDSA`
5. **Convert DER→raw** signature format (r + s, 32 bytes each)
6. **Build JWK** `{ crv: 'P-256', kty: 'EC', x, y }`
7. **POST attestation** → `{ viewer_id, device_id, challenge_id, nonce, signature, public_key: jwk, client, storage, attributes }`
8. **POST playback** → `{ fingerprint: { token, viewer_id, device_id, confidence } }`
9. **AES-256-GCM decrypt** → key = `key_parts[0] + key_parts[1]`, decrypt payload → `{ sources: [{ url }] }`

## Porting Patterns (Kotlin → JavaScript)

| Kotlin | JavaScript (Node.js) |
|--------|---------------------|
| `android.util.Base64` | `Buffer.toString('base64url')` |
| `javax.crypto.Cipher` | `crypto.createDecipheriv()` |
| `javax.crypto.spec.GCMParameterSpec` | `decipher.setAuthTag()` |
| `KeyPairGenerator` | `crypto.generateKeyPairSync()` |
| `Signature.getInstance("SHA256withECDSA")` | `crypto.createSign('SHA256')` |
| `ECGenParameterSpec("secp256r1")` | `{ namedCurve: 'P-256' }` |
| `OkHttp + Retrofit` | `axios` |
| `Jsoup.select()` | `cheerio` |
| DER→Raw converter | Manual byte parsing (30 02 rLen rBytes 02 sLen sBytes) |
