Skip to content

feat(madara): list coin-locked chapters behind a source option (Tangerine Archive) - #4

Closed
RibatTRW wants to merge 2 commits into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-premium-madara-shared
Closed

RibatTRW wants to merge 2 commits into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-premium-madara-shared

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Stacked candidate fix for lnreader#2564 (base: fm/lnreader-plugins-2555-tangerine). PR lnreader#2564 deliberately
omits coin-locked chapters; this PR lists them and gives them an honest locked experience instead of
a blank chapter. The locked body stays server-gated — nothing here bypasses the coin-lock.

What changed (3 files, flag-gated)

plugins/multisrc/madara/template.ts — new opt-in includeLockedChapters source option, default off:

  • In parseNovel, when the option is on and a row is premium-block with href="#", the
    /series/<slug>/chapter-<number>/ path is rebuilt from the chapter number in the row title, so the
    row is listed with the free chapters. The slug follows the site's own rules, verified against the
    free rows: Chapter 27.2 → chapter-27-2; a non-ASCII token straight after the number is part of
    the slug (Chapter 176 🔞 → chapter-176-%F0%9F%94%9E); a - subtitle is dropped
    (Chapter 242 - 🔞 → chapter-242). Rows whose title has no number are still skipped.
    releaseTime is null for rebuilt rows because the site only shows a countdown or TBA, never a
    publication date (the template's parseData would otherwise turn "Unlocked in 4 weeks" into a bogus
    date four weeks in the past).
  • In parseChapter, when the option is on: a lock notice inside .reading-content
    (.content-blocked / .premium-block / "chapter is locked") throws
    This chapter is locked and costs <n> coins. Unlock it with a site account to read it here.;
    a page with no .reading-content at all (e.g. a rebuilt URL that falls back to the series page)
    throws a clear "no readable content" error instead of returning '' (blank).
    Detection is scoped to .reading-content, because free chapter pages carry a
    div.nav-next.premium...premium-block outside it that would otherwise look locked.
    It is decided from the fetched page, not from the list class, so a row the list marks free but
    whose page is still gated is covered too.

plugins/multisrc/madara/sources.json — tangerinearchive opts in (includeLockedChapters: true)
and versionIncrements: 1 (plugin 2.2.1).

plugins/multisrc/madara/README.md — documents the new option.

Verified anonymously, read-only (no login, no cookies, no purchase, no unlock request)

  • Live list vs. parser: garden-of-may parses 212 = 52 premium + 160 free and
    villain-let-me-touch-you parses 191 = 46 premium + 145 free, both exactly matching an
    independent anonymous POST /series/<slug>/ajax/chapters/. Every premium row's rebuilt path matches
    its number; free chapters still parse (chapter-1 7120 chars, chapter-160 6538 chars).
  • parseChapter: garden-of-may/chapter-161/ and /chapter-212/ (10 coins) throw the locked error;
    it-seems-like-.../chapter-244/ (12 coins) throws with the right price; the previously-failing
    emoji rows now resolve to their own gated pages (villain-let-me-touch-you/chapter-176-%F0%9F%94%9E/
    and .../chapter-175-%F0%9F%94%9E/) and throw the locked error; free chapters return prose.
  • Read-time lock state beats the list class: holy-night-my-husband-is-definitely-a-paladin/chapter-359/
    is listed as a free row with a real href on the site, but its page is gated
    (premium coin-12 ... content-blocked). With this change it throws the locked error ("costs 12 coins")
    instead of returning blank/leaking anything.
  • Slug rule validated against the real hrefs of 1904 free rows from 14 series: 1886 match exactly,
    17 differ only by percent-encoding case, and the single miss is a WordPress duplicate-slug uniquifier
    (chapter-55_1) whose premium equivalent is the documented duplicate-number case below.
  • Reconstruction sweep: 152 premium rows across 4 series fetched and checked against the page's
    #wp-manga-current-chap[data-id] == the ajax row's data-chapter-<id>. Every row now resolves to its
    own gated page except villain-let-me-touch-you's duplicate Chapter 166 pair (two rows, one
    /chapter-166/ path; both are locked and show the same notice — the colliding row's own slug is
    undiscoverable). An offline census of 2486 rows found no free↔premium number collision, so a
    premium row cannot surface free prose, and parseChapter refuses any page without a chapter body.
  • Dead ends checked so the listing can be honest about the alternatives: alternate render formats
    (/amp/, /print/, /feed/, /embed/, ?amp, ?print=1, ?feed=rss2, ?preview=, ?style=,
    ?_format=json, ?output=, ?ajax=1, ?nocache=, ?display=full) either return the same gated
    page or a series-page soft-404; oEmbed returns series metadata only; /wp-json/ exposes no chapter
    route (wp/v2 has no chapter CPT, wp-manga/v1 only has a cache-stats route); /wp-sitemap.xml and
    the wp-manga-chapters-sitemap{1..8}.xml family list only free chapters (2289 URLs, no premium URL);
    manga_get_reading_nav with the theme's own params returns all 212 options but every one of the 52
    premium options carries data-redirect="#"; chapter pages are cf-cache-status: DYNAMIC with
    no-store, and crawler user agents get the same gated page (Googlebot: 403). There is no anonymous
    route to the locked body, so this PR does not look for one.

Checks run

  • npm run build:multisrc and npm run build:compile pass.
  • npm run check:plugin -- 'plugins/english/TangerineArchive[madara].ts' passes (popularNovels 12,
    searchNovels PASS, parseNovel 173 chapters — the same novel returned 53 before — parseChapter
    14497 chars on a free chapter).
  • Shared-template guard: includeLockedChapters is referenced only in the template and the
    tangerinearchive entry (git grep), and unrelated
    npm run check:plugin -- 'plugins/english/SleepyTranslations[madara].ts' passes all four checks
    (16 novels / 44 chapters / 9872 chars).
  • Prettier and ESLint clean on the changed files.

Not tested / not claimed

  • No entitled-session test. The locked prose could not be read without logging in and spending
    coins, so this PR does not read it and does not claim to. The listed premium chapters remain
    server-gated and now fail loudly instead of returning blank text.
  • The reader app's rendering of the thrown error was not exercised, only the plugin-level error.
  • releaseTime: 'LL' for Tangerine's free chapters is a pre-existing shared-template issue
    (dayjs().format('LL') without localizedFormat) and is intentionally left out of scope.

Authored with an AI agent, per the repository's commit guidance (Co-Authored-By: pi <pi@local>).

RibatTRW and others added 2 commits September 23, 2026 19:54
Madara sites that use the wp-manga-chapter-coin plugin hide the premium
rows from the chapter list (href="#") while the chapter itself is still
served at /series/<slug>/chapter-<number>/ with only a "This chapter is
locked!" notice in .reading-content. Excluding those rows makes the list
understate the novel and hides which chapters exist.

Add an opt-in includeLockedChapters source option (default off) that:

- rebuilds a premium row's /chapter-<number>/ path from its title and
  keeps the row listed next to the free chapters;
- throws a clear "chapter is locked" error in parseChapter (including the
  coin price from the page's coin-N class) instead of returning an empty
  chapter, and also fails loud when a rebuilt URL lands on a page with no
  chapter body at all;
- reports no releaseTime for rebuilt rows, because the site only shows a
  countdown or "TBA" (never a publication date).

tangerinearchive opts in and its plugin version is incremented to 2.2.1.
The locked body stays server-gated: no anonymous path returns it, so the
listed premium chapters remain unreadable and now say so honestly.

Co-Authored-By: pi <pi@local>
The site keeps a non-ASCII token that follows the chapter number in the
chapter slug ("Chapter 176 🔞" -> /chapter-176-%F0%9F%94%9E/) while a
" - subtitle" is dropped ("Chapter 242 - 🔞" -> /chapter-242/). Without
that rule the two emoji chapters listed by the option resolved to the
series page instead of their own locked page.

Verified against the free rows of 14 series (1904 rows): the rule matches
1886 slugs exactly, 17 more differ only by percent-encoding case, and the
single miss is a WordPress duplicate-slug uniquifier (chapter-55_1) whose
premium equivalent is the already-documented duplicate-number collision.

Co-Authored-By: pi <pi@local>
@RibatTRW RibatTRW closed this Sep 23, 2026
@RibatTRW
RibatTRW deleted the fm/tangerine-premium-madara-shared branch September 23, 2026 13:00
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