fix(wtrlab): follow the reader API's new content_url - #2628
Conversation
Checklist Update version code if an existing plugin was modified Test changes in Plugin Playground or the app Reference related issues in the PR body (e.g. Closes #xyz) Commit messages follow type(scope): description Fixes lnreader#2627 Closes lnreader#2627 WTR-LAB chapters stopped loading — the reader API moved the content wtr-lab changed /api/reader/get today. It no longer carries the chapter inline; it returns an envelope with a pointer to the payload: before { success, data: { data: { body, glossary_data, model, … } } } after { success, chapter: { title, locked, … }, tasks: [], content_url } GET content_url → { success, data: { …, data: { body, glossary_data, model, … } } } The plugin reads data.data.body off the first response, which no longer exists there, so every chapter fails. The request itself still returns 200 with success: true — it just has no body in it, which is why this surfaces as "no usable response" rather than an HTTP error. Verified live against serie 98170, chapter 2: mode payload at content_url ai body array of 136 strings, glossary_data with 20 terms, model: gemini-3.5-flash-lite web body string prefixed arr: plus encrypted — the existing decrypt path webplus same as web So the payload shape is unchanged — only its location moved. The existing decryption, glossary substitution and translation-mode handling all still apply once the pointer is followed. The change Follow content_url when present. It is returned relative, so it is resolved against the site. Fall back to the old inline data.data shape when that is what comes back, so the plugin works whichever the server returns — useful if this is still rolling out, and harmless afterwards. Chapter title now comes from the envelope (chapter.title, as added in lnreader#2538), with the payload's own title as a fallback. The failure message distinguishes "the server accepted the request but returned no chapter content" from "no usable response", so the next shape change is easier to spot. Testing Built and read in the app against the live site: AI, Web and Web+ all load again, glossary terms substitute, and the chapter title still renders. Eight regression checks cover this specifically — the new shape renders, content_url is followed exactly once and resolved to an absolute URL, the old inline shape still works without a second request, and a payload with neither raises an explicit error instead of throwing on undefined. Note on formatting prettier --check already fails on this file on master — one line in the AES helper, which looks like a prettier version difference rather than anything in this change. I've left it untouched rather than reformatting an unrelated line.
|
| const url = /^https?:\/\//i.test(contentUrl) | ||
| ? contentUrl | ||
| : this.site.replace(/\/$/, '') + contentUrl; | ||
|
|
||
| const res = await fetchApi(url, { | ||
| headers: { | ||
| 'Accept': 'application/json', | ||
| ...(cookie ? { Cookie: cookie } : {}), |
There was a problem hiding this comment.
If the reader API returns an absolute content_url on another host, resolveChapterContent sends that host the user's full session cookie. Check the host before attaching the cookie, or fetch off-site content without it.
How this was verified: An unrestricted absolute URL reaches fetchApi with the stored session cookie in its Cookie header.
| const content = usedType | ||
| ? await this.resolveChapterContent(parsedJson, cookie) | ||
| : null; | ||
|
|
||
| if (!usedType || !content || content.body === undefined) { |
There was a problem hiding this comment.
If the preferred mode's content_url fails but Web is available, the reader throws instead of trying Web. The loop stops when the first reader request succeeds, before resolveChapterContent checks whether its link provides a chapter. Check the content before ending the loop so the configured fallback can run.
|
fixed above concerns identified by greptile-apps Update version code if an existing plugin was modified Fixes #2627 Closes #2627 WTR-LAB chapters stopped loading — the reader API moved the content wtr-lab changed /api/reader/get today. It no longer carries the chapter inline; it returns an envelope with a pointer to the payload: before { success, data: { data: { body, glossary_data, model, … } } } after { success, chapter: { title, locked, … }, tasks: [], content_url } The plugin reads data.data.body off the first response, which no longer exists there, so every chapter fails. The request itself still returns 200 with success: true — it just has no body in it, which is why this surfaces as "no usable response" rather than an HTTP error. Verified live against serie 98170, chapter 2: mode payload at content_url So the payload shape is unchanged — only its location moved. The existing decryption, glossary substitution and translation-mode handling all still apply once the pointer is followed. The change Built and read in the app against the live site: AI, Web and Web+ all load again, glossary terms substitute, and the chapter title still renders. Eight regression checks cover this specifically — the new shape renders, content_url is followed exactly once and resolved to an absolute URL, the old inline shape still works without a second request, and a payload with neither raises an explicit error instead of throwing on undefined. Review follow-up Two findings from the review, both fixed: The session cookie must not leave the source. It was attached to the content_url request unconditionally. content_url is chosen by the server, so an absolute one could point anywhere. It is now sent only when the resolved URL is on the source's own host or a subdomain of it — a lookalike such as wtr-lab.com.evil.net does not match. Same-site requests still carry it, which auth-gated payloads need. The Web fallback has to survive. Resolving the payload after the mode loop meant a healthy envelope ended the loop, so a mode that returned no content failed outright instead of falling through to Web. Resolution now happens inside the loop, and a mode only counts as successful once it has a body; otherwise the reason is logged and the next mode is tried. This restores the pre-existing behaviour, which the inline shape had given for free. Eight further checks cover these: the cookie is withheld from an off-site or lookalike host but still sent to the source and its subdomains, and a chapter whose AI payload is missing still renders from Web, with the AI failure reported rather than hidden. Note on formatting prettier --check already fails on this file on master — one line in the AES helper, which looks like a prettier version difference rather than anything in this change. I've left it untouched rather than reformatting an unrelated line. |
Checklist
Update version code if an existing plugin was modified
Test changes in Plugin Playground or the app
Reference related issues in the PR body (e.g. Closes #xyz)
Commit messages follow type(scope): description
Fixes #2627
Closes #2627
WTR-LAB chapters stopped loading — the reader API moved the content
wtr-lab changed /api/reader/get today. It no longer carries the chapter inline; it returns an envelope with a pointer to the payload:
before { success, data: { data: { body, glossary_data, model, … } } }
after { success, chapter: { title, locked, … }, tasks: [], content_url }
GET content_url →
{ success, data: { …, data: { body, glossary_data, model, … } } }
The plugin reads data.data.body off the first response, which no longer exists there, so every chapter fails. The request itself still returns 200 with success: true — it just has no body in it, which is why this surfaces as "no usable response" rather than an HTTP error.
Verified live against serie 98170, chapter 2:
mode payload at content_url
ai body array of 136 strings, glossary_data with 20 terms, model: gemini-3.5-flash-lite
web body string prefixed arr: plus encrypted — the existing decrypt path
webplus same as web
So the payload shape is unchanged — only its location moved. The existing decryption, glossary substitution and translation-mode handling all still apply once the pointer is followed.
The change
Follow content_url when present. It is returned relative, so it is resolved against the site.
Fall back to the old inline data.data shape when that is what comes back, so the plugin works whichever the server returns — useful if this is still rolling out, and harmless afterwards.
Chapter title now comes from the envelope (chapter.title, as added in #2538), with the payload's own title as a fallback.
The failure message distinguishes "the server accepted the request but returned no chapter content" from "no usable response", so the next shape change is easier to spot.
Testing
Built and read in the app against the live site: AI, Web and Web+ all load again, glossary terms substitute, and the chapter title still renders.
Eight regression checks cover this specifically — the new shape renders, content_url is followed exactly once and resolved to an absolute URL, the old inline shape still works without a second request, and a payload with neither raises an explicit error instead of throwing on undefined.
Note on formatting
prettier --check already fails on this file on master — one line in the AES helper, which looks like a prettier version difference rather than anything in this change. I've left it untouched rather than reformatting an unrelated line.