Skip to content

fix(wtrlab): follow the reader API's new content_url - #2628

Merged
rajarsheechatterjee merged 6 commits into
lnreader:masterfrom
ShakeyHands91:master
Oct 4, 2026
Merged

rajarsheechatterjee merged 6 commits into
lnreader:masterfrom
ShakeyHands91:master

Conversation

@ShakeyHands91

Copy link
Copy Markdown
Contributor

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.

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.
@ShakeyHands91 ShakeyHands91 mentioned this pull request Oct 2, 2026
5 tasks done
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

[Medium risk] Updates a plugin to handle a changed API response format.

This PR is not ready to merge until it protects the session cookie and lets Web fallback run when a content link fails.

Findings

  1. P1 Security Session cookie sent off-site ▶
  2. P1 Web fallback never runs ▶

Summary

The WTR-LAB plugin now follows the reader API’s content_url to load chapter text, while still accepting inline chapter data. This keeps chapter loading compatible with both response shapes.

  • Uses the resolved chapter body and glossary, and prefers the title from the reader response envelope.
  • Gives a clearer message when a successful request has no chapter content.
  • Updates the plugin version from 1.2.2 to 1.2.3.

Reviews (1) · Last reviewed commit: "Add files via upload"

Comment thread plugins/english/wtrlab.ts Outdated
Comment on lines +83 to +90
const url = /^https?:\/\//i.test(contentUrl)
? contentUrl
: this.site.replace(/\/$/, '') + contentUrl;

const res = await fetchApi(url, {
headers: {
'Accept': 'application/json',
...(cookie ? { Cookie: cookie } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Session cookie sent off-site

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.

Comment thread plugins/english/wtrlab.ts Outdated
Comment on lines +798 to +802
const content = usedType
? await this.resolveChapterContent(parsedJson, cookie)
: null;

if (!usedType || !content || content.body === undefined) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Web fallback never runs

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.

@ShakeyHands91

Copy link
Copy Markdown
Contributor Author

fixed above concerns identified by greptile-apps

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.

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.

This was referenced Oct 2, 2026
Closed
@rajarsheechatterjee
rajarsheechatterjee merged commit 33df437 into lnreader:master Oct 4, 2026
3 checks passed
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.

Wrt changed API setting

2 participants