Skip to content

fix(english/wuxiaworld): decode gRPC-web frames directly to load novels - #2833

Open
Nreel wants to merge 1 commit into
lnreader:masterfrom
Nreel:fix/english-wuxiaworld-grpc-decode
Open

Nreel wants to merge 1 commit into
lnreader:masterfrom
Nreel:fix/english-wuxiaworld-grpc-decode

Conversation

@Nreel

@Nreel Nreel commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Root cause

WuxiaWorld only speaks gRPC-web (api2.wuxiaworld.com), so novel/chapter loading went through @libs/fetch's fetchProto. The app implementation decodes the response with FileReader.readAsDataURL and slices a fixed data:application/octet-stream;base64, prefix off the result, but React Native builds that prefix from the blob's own MIME type, which the app's fetch implementation takes from the response headers.

WuxiaWorld answers with application/grpc-web, whose prefix is shorter than the hardcoded one. The fixed slice therefore swallows the first bytes of the protobuf message frame, and protobufjs rejects the payload with illegal tag: field number 0. parseNovel never resolves, so the novel screen loads forever (#2830). The same failure is why #1679 reported "Can't parse page!" for this plugin.

Change

Replaced fetchProto with a small gRPC-web frame reader/writer in the plugin (plugins/english/wuxiaworld.ts, v0.5.1 → 0.6.0):

  • Requests are encoded directly into the 5-byte gRPC-web frame (GetNovelRequest slug, GetChapterListRequest novelId, GetChapterRequest novel+chapter slugs) and sent with Response-compatible Uint8Array bodies.
  • Responses are read with response.arrayBuffer(), which returns raw bytes and sidesteps the MIME-prefix bug entirely. Frames with flag 0x80 (trailers) and malformed/short payloads are ignored rather than thrown, so a bad response yields an empty chapter list instead of an endless spinner.
  • Protobuf fields used by this API (varint, length-delimited, sfixed32, nested messages) are decoded without protobufjs; UTF-8 decoding is hand-rolled so no TextDecoder is required in the Hermes runtime. bigint is not used.
  • Existing behavior preserved: relative novel/chapter paths, chapter grouping by volume via page, locked-chapter markers against maxFreeChapter (default 50), status mapping, release dates, cover/author/genres/summary from the API.

Verification

npm run check:plugin -- plugins/english/wuxiaworld.ts against the live site:

Check Result Details
popularNovels PASS 50 novels
searchNovels PASS 1 result for "Child of Light"
parseNovel PASS 357 chapters
parseChapter PASS 8351 chars

Also eslint, tsc --project tsconfig.production.json, and prettier --check all clean.

Live-check output
| Check | Result | Details |
| --- | --- | --- |
| `popularNovels` | ✅ PASS | 50 novels |
| `searchNovels` | ✅ PASS | 1 results for "Child of Light" |
| `parseNovel` | ✅ PASS | 357 chapters |
| `parseChapter` | ✅ PASS | 8351 chars |
Result: ✅ Passed — all checks succeeded.

Closes #2830

The app's fetchProto slices a fixed data:application/octet-stream;base64, prefix off the readAsDataURL result, while React Native builds that prefix from the blob MIME type taken from the response headers. WuxiaWorld answers with application/grpc-web, whose shorter prefix makes the fixed slice swallow the message frame's first bytes, so protobufjs throws 'illegal tag: field number 0' and parseNovel never resolves. Encode and decode gRPC-web frames with Response.arrayBuffer() instead.

Closes lnreader#2830

Co-Authored-By: omp <noreply@omp.local>
@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects novel and chapter loading for WuxiaWorld users.

The PR appears safe to merge, with a non-blocking issue in locked-chapter warnings.

Findings

  1. P2 Locked chapters lose their warning ▶

Summary

Replaces fetchProto with direct gRPC-web requests and byte decoding in wuxiaworld.ts.

  • WuxiaWorld loads novels and chapters from raw gRPC-web frames.

Reviews (1) · Last reviewed commit: "fix(english/wuxiaworld): decode gRPC-web..." · Reviewed by Greptile

Comment on lines +342 to +343
if (field === 1 && wireType === 0) {
info.isChapterUnlocked = readVarint(reader) !== 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Locked chapters lose their warning

decodeRelatedChapterUserInfo reads isChapterUnlocked as a plain boolean, but the removed schema defines it as a nested BoolValue. If the server sends that wrapper, the decoder skips it and returns {}. This loses the explicit false and also prevents the chapter-number fallback because relatedUserInfo is truthy. Readers then see locked chapters without the 🔒 warning.

Decode the nested BoolValue, including its default false, to preserve the warning.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WuxiaWorld Nor loading

1 participant