refactor: update m3u8 source fetching logic#164
Conversation
If I write the original data fetched from fetchFile it won't read the ts segment to combine into mp4, so I split the image and ts segment. Then write the ts segment and it works.
Add IEND_IMAGE constant for MP4 conversion.
Moved the IEND_IMAGE constant to the end of the file for better organization.
Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
|
|
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
Review Summary by QodoRefactor m3u8 source fetching with iframe extraction and AES-GCM decryption
WalkthroughsDescription• Refactor m3u8 source fetching to use iframe-based extraction • Replace WASM decryption with native AES-GCM Web Crypto API • Add new getSourceM3u8 function for iframe player integration • Switch from direct fetch to POST request via post helper • Add Google Storage CDN constant for URL resolution Diagramflowchart LR
A["PlayerLink Request"] --> B["POST /ajax/player"]
B --> C["Check playTech"]
C -->|iframe| D["getSourceM3u8"]
D --> E["Extract id & token from HTML"]
E --> F["Fetch m3u8 from Google CDN"]
F --> G["Check if encrypted"]
G -->|encrypted| H["decryptM3u8 with AES-GCM"]
G -->|not encrypted| I["Use as-is"]
H --> J["Return config with m3u8"]
I --> J
File Changes1. src/apis/runs/ajax/player-link.ts
|
Code Review by Qodo
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 21 |
| Duplication | 0 |
TIP This summary will be updated as you push new changes. Give us feedback
| if (config.playTech === "iframe") { | ||
| const { m3u8: encryptedM3u8, headers } = await getSourceM3u8(config.link) | ||
|
|
There was a problem hiding this comment.
1. Wrong getsourcem3u8 argument 🐞 Bug ≡ Correctness
In PlayerLink, getSourceM3u8 is called with config.link, but the codebase models link as an
array of {file,label,...} objects, not a URL string, so the request URL becomes "[object Object]"
and iframe playback breaks. This is masked by JSON.parse(data) returning any, so TypeScript
won’t protect against it.
Agent Prompt
### Issue description
`PlayerLink` calls `getSourceM3u8(config.link)` but `getSourceM3u8` expects a URL string.
### Issue Context
In this codebase, `link` is modeled as an array of link objects (`{ file, label, ... }[]`). Passing that array/object into a string template will yield `"[object Object]"`, producing an invalid GET URL.
### Fix Focus Areas
- src/apis/runs/ajax/player-link.ts[67-69]
- src/logic/get-source-m3u8.ts[3-9]
- src/pages/phim/_season.interface.ts[30-54]
### Suggested fix
Decide the actual server response shape for `playTech === "iframe"` and:
- If `config.link` is an array: pass the iframe URL as `config.link[0].file` (or whichever element contains the iframe/player URL).
- If `config.link` is actually a string only in iframe mode: update typings accordingly (e.g., a discriminated union for `playTech`) and ensure downstream code always receives the normalized array after you rewrite `config.link`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const isEncrypted = encryptedM3u8.match(/[?&]_c=[0-9]+/) | ||
| let finalM3u8 = "" | ||
|
|
||
| switch ( | ||
| (item.label as typeof item.label | undefined)?.toUpperCase() as | ||
| | Uppercase<Exclude<typeof item.label, undefined>> | ||
| | undefined | ||
| ) { | ||
| case "HD": | ||
| if (item.preload) item.label = "FHD|HD" | ||
| break | ||
| case undefined: | ||
| item.label = "HD" | ||
| break | ||
| } | ||
| item.qualityCode = getQualityByLabel(item.label) | ||
| item.type ??= "mp4" | ||
| }) | ||
| ) | ||
| if (isEncrypted) { | ||
| finalM3u8 = await decryptM3u8(encryptedM3u8, headers) | ||
| } | ||
| config.link = [ | ||
| { | ||
| file: `data:application/vnd.apple.mpegurl;base64,${btoa(unescape(encodeURIComponent(finalM3u8)))}`, | ||
| label: "FHD|HD", | ||
| preload: "auto", | ||
| type: "hls" | ||
| } | ||
| ] |
There was a problem hiding this comment.
2. Blank m3u8 returned 🐞 Bug ≡ Correctness
In iframe mode, finalM3u8 is initialized to an empty string and only set when _c= is detected; otherwise the returned HLS data URI contains an empty playlist. This causes playback/download to fail whenever the fetched playlist is already unencrypted or uses a different encryption marker.
Agent Prompt
### Issue description
When the fetched playlist is not encrypted (or not matched by the `_c=` regex), `finalM3u8` remains empty but is still used to create the `data:` URL.
### Issue Context
This makes iframe playback return an empty playlist.
### Fix Focus Areas
- src/apis/runs/ajax/player-link.ts[70-83]
### Suggested fix
Set `finalM3u8` to the fetched playlist by default, and only replace it with decrypted content when needed:
- `let finalM3u8 = encryptedM3u8`
- `if (isEncrypted) finalM3u8 = await decryptM3u8(encryptedM3u8, headers)`
Also consider renaming `encryptedM3u8` to `rawM3u8` to avoid confusion.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (finalUrl.startsWith("/")) { | ||
| finalUrl = cdn_google + finalUrl | ||
| } else if (!finalUrl.startsWith("http")) { | ||
| finalUrl = cdn_google + "/" + finalUrl | ||
| } |
There was a problem hiding this comment.
3. Cdn_google not imported 🐞 Bug ≡ Correctness
cdn_google is referenced in decrypt-hls-animevsub.ts and get-source-m3u8.ts but is never imported, causing a runtime ReferenceError (and typically a TS compile error). This breaks playlist URL rewriting and playlist fetching.
Agent Prompt
### Issue description
`cdn_google` is used without being imported in two files, making it undefined at runtime.
### Issue Context
`cdn_google` is exported from `src/constants.ts`.
### Fix Focus Areas
- src/logic/decrypt-hls-animevsub.ts[123-134]
- src/logic/get-source-m3u8.ts[1-24]
- src/constants.ts[50-64]
### Suggested fix
Add:
- `import { cdn_google } from "src/constants"`
into both `decrypt-hls-animevsub.ts` and `get-source-m3u8.ts`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const idRegex = /const\s+id\s*=\s*["']([^"']+)["']/i | ||
| const tokenRegex = /const\s+avsToken\s*=\s*["']([^"']+)["']/i | ||
|
|
||
| const idMatch = html.match(idRegex) | ||
| const tokenMatch = html.match(tokenRegex) | ||
|
|
||
| const id = idMatch ? idMatch[1] : null | ||
| const avsToken = tokenMatch ? tokenMatch[1] : null | ||
|
|
||
| const m3u8Response = await get({ | ||
| url: `${cdn_google}/playlist/${id}/playlist.m3u8?token=${avsToken}#animevsub-vsubo`, | ||
| responseType: "text" | ||
| }) |
There was a problem hiding this comment.
4. Null id/token request 🐞 Bug ☼ Reliability
getSourceM3u8 continues even when it fails to extract id or avsToken, producing a request to /playlist/null/playlist.m3u8?token=null. This will fail downstream with less actionable errors than a clear parse failure.
Agent Prompt
### Issue description
If HTML parsing fails, `id` and/or `avsToken` becomes `null` but the code still builds a playlist URL using those values.
### Issue Context
This generates invalid URLs and hides the true root cause (parsing failure).
### Fix Focus Areas
- src/logic/get-source-m3u8.ts[12-24]
### Suggested fix
After extracting matches:
- If `!id` or `!avsToken`, throw a descriptive error (include a small snippet or markers to help debugging).
- Use `encodeURIComponent(avsToken)` when constructing the query string to avoid malformed URLs if the token contains special characters.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Can you create a pull request just for comments? I already have a Rust decoder and I don't want the decoders to appear on public repositories. |
|
I have created a new PR #166 containing only the comments, so I will close this one. Thank you for your guidance |
Changes:
src/apis/runs/ajax/player-link.ts: Use a POST request to fetch/ajax/playerfrom the server, then decrypt it using the newdecryptM3u8function to retrieve the original source.src/constants.ts: Add thecdn_googlevariable to store the Google Storage CDN address.src/logic/decrypt-hls-animevsub.ts: Replace the old WASM decryption with the new AES-GCM decryption method.Added:
src/logic/get-source-m3u8.ts: Add thegetSourceM3u8function to extract the source, IV, and salt via the iframe player.