Skip to content

fix(madara): list Tangerine Archive coin-locked chapters behind an opt-in option - #9

Closed
RibatTRW wants to merge 1 commit into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-replica-madara-shared
Closed

RibatTRW wants to merge 1 commit into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-replica-madara-shared

Conversation

@RibatTRW

Copy link
Copy Markdown
Owner

What this is

Candidate fix for the premium (coin-locked) chapter problem in the Tangerine Archive source from
PR lnreader#2564, stacked on that PR's branch (fm/lnreader-plugins-2555-tangerine).

Tangerine Archive links coin-locked chapters as href="#", so the plugin dropped them entirely and
opening a locked chapter returned an empty body. This change lists them with the site's real
/chapter-<n>/ path and makes parseChapter report what actually happened instead of blank text.

What changed

All of it is additive and behind a new default-off source option, so no other Madara source
changes behavior:

  • plugins/multisrc/madara/template.ts
    • new includeLockedChapters?: boolean in MadaraOptions;
    • parseNovel: for premium-block rows whose href is #, rebuild the real path from the
      listing's own anchor text instead of dropping the row;
    • parseChapter: when the flag is on, read the fetched page and throw a clear error for a locked
      page (coin price from the page's coin-N gate class, plus the listing's free-unlock countdown or
      TBA), for a derived URL that fell back to the series page (no chapter page), and for a derived
      URL that resolves to a different chapter (duplicate chapter numbers).
    • releaseTime: null for rebuilt rows: the listing carries only a countdown/TBA, which parseData
      would turn into a bogus past date.
  • plugins/multisrc/madara/sources.json: tangerinearchive opts in
    (includeLockedChapters: true, hasLocked: true for the app's existing hide-locked switch,
    versionIncrements: 1 -> plugin 2.2.1).
  • plugins/multisrc/madara/README.md: documents the new option.

The anchor-text rule (non-uniform by design)

Verified against the live listings; the rule is not a plain Chapter (\d+) match:

listing text rebuilt path
Chapter 250 - (End of the Side Stories) chapter-250/ (title suffix dropped)
Chapter 239 - SS-IV. (AU) chapter-239/
Chapter 27.2 chapter-27-2/ (dot -> dash)
Chapter 176 🔞 chapter-176-%F0%9F%94%9E/ (non-ASCII token directly after the number is part of the site's slug)
Chapter 242 - 🔞 chapter-242/ (a - separator starts a title, not the slug)

The page-identity guard (the gap this PR closes beyond the sibling candidate)

Each rebuilt row's own data-chapter-<id> is carried into parseChapter and compared with the
fetched page's #wp-manga-current-chap[data-id]. villain-let-me-touch-you has two premium rows
numbered Chapter 166 (ids 16701 and 16700); /chapter-166/ serves 16700, so the notice names the
chapter the URL actually serves instead of presenting it as the row the reader picked. A derived URL
that silently renders the series page throws a "no chapter page" error instead of returning the
series summary.

What was probed (live, anonymous, read-only - no cookies, no sessions, no nonces, no purchases)

  • npm run build:compile and npm run build:multisrc pass; eslint and prettier --check on the
    changed files pass.
  • npm run check:plugin on the generated Tangerine plugin: PASS (popularNovels 12,
    searchNovels PASS, parseNovel 173 chapters, parseChapter 14497 chars).
  • Shared-template guard: npm run check:plugin on two unrelated Madara sources with the flag off -
    SleepyTranslations[madara] PASS (16 novels / 44 chapters / 9872 chars) and
    Lovelyblossoms[madara] PASS (16 novels / 5 chapters / 15373 chars). A third,
    CitrusAurora[madara], also passes in the custom probe.
  • Custom behavioral probe (.scratch/probe-plugin.mjs, 28/28 checks) against the generated plugin:
    • garden-of-may: 212 chapters listed = 160 free + 52 locked, exactly the live ajax list; every
      locked path equals the path derived independently from the live row, every free path equals the
      live href;
    • free chapter-160/ parses 6538 chars of prose; locked chapter-161/ throws
      "it costs 10 coins, it unlocks for free in 1 day"; locked chapter-212/ throws TBA; a wrong
      derived slug throws the missing-page error;
    • villain-let-me-touch-you: 191 chapters = live list; Chapter 176 🔞 rebuilds the emoji slug
      and throws the locked error; the duplicate Chapter 166 pair is listed and the read names the
      served chapter;
    • in-the-doghouse Chapter 250 - (End of the Side Stories) -> chapter-250/;
      how-to-survive-as-a-supporting-male-lead Chapter 27.2 -> chapter-27-2/;
    • 4 sampled rebuilt paths resolve to the row's own chapter page (page data-id == row id, gate
      present);
    • the three unrelated sources have includeLockedChapters undefined and take the unchanged path.
  • Independent catalogue census (19 series via wp-manga-sitemap.xml, 3359 rows: 672 premium +
    2687 free): the slug rule reproduces 2686/2687 free hrefs (2669 identical + 17 equal after
    percent-decoding; the single miss is a free duplicate with a real href), and 0 premium rows lack
    a leading Chapter <n>.

What was NOT tested

  • No entitled session. Locked bodies are gated server-side; nothing here logs in, unlocks, or
    spends coins, so the actual prose of a still-locked chapter was not read. The plugin reports the
    gate honestly instead of serving it. The only routes to the prose remain the site's own scheduled
    free unlock and an owner-provided entitled session.
  • No purchase/buy endpoint, no nonce or session forging, no third-party copies of the gated text.
  • The countdown text comes from the listing row (it is not on the chapter page); if the listing
    cannot be read in the same session, the locked error still fires from the page alone, just without
    the countdown.
  • Only tangerinearchive enables the option; other Madara sources were checked for non-regression,
    not for the feature.

Tangerine Archive links coin-locked chapters as `#`, so the plugin dropped
them and opening a locked chapter returned an empty body. Add an additive,
default-off `includeLockedChapters` source option that keeps those rows in the
list with the site's real `/chapter-<n>/` path and makes `parseChapter` report
what actually happened instead of blank text:

- Rebuild the locked path from the row's anchor text. The anchor text is not
  uniform: `Chapter 250  - (End of the Side Stories)` drops the title suffix,
  `Chapter 27.2` becomes `chapter-27-2`, and a non-ASCII token directly after
  the number (`Chapter 176 🔞`) is part of the site's own slug
  (`chapter-176-%F0%9F%94%9E`), while ` - ` starts a title suffix.
- Throw a clear locked error naming the coin price and, when the listing has
  one, the free-unlock countdown or TBA.
- Carry each rebuilt row's `data-chapter-<id>` into `parseChapter` and check it
  against the fetched page, so a derived URL that lands on a different chapter
  (duplicate chapter numbers) is named rather than silently served as the
  listed one, and a derived URL that falls back to the series page throws a
  "no chapter page" error instead of returning the series summary.
- No release time for rebuilt rows: the listing only carries a countdown or
  TBA, which `parseData` would turn into a bogus past date.

Only tangerinearchive opts in (plus `hasLocked` for the app's hide-locked
switch, version 2.2.1). Other Madara sources take the old code path unchanged.

Co-Authored-By: pi <noreply@pi.dev>
@RibatTRW RibatTRW closed this Sep 23, 2026
@RibatTRW
RibatTRW deleted the fm/tangerine-replica-madara-shared branch September 24, 2026 01:06
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.

1 participant