Skip to content

feat(madara): list tangerinearchive premium chapters with lock-state labels - #6

Closed
RibatTRW wants to merge 1 commit into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-premium-tba-split
Closed

RibatTRW wants to merge 1 commit into
fm/lnreader-plugins-2555-tangerinefrom
fm/tangerine-premium-tba-split

Conversation

@RibatTRW

@RibatTRW RibatTRW commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Stacked candidate fix for lnreader#2564 (base is that PR's branch fm/lnreader-plugins-2555-tangerine, not the default branch).

What this fixes

PR 2564 intentionally drops the coin-locked (premium) chapters of Tangerine Archive, so a reader sees 160 chapters for a 212-chapter novel. Premium rows are published with href="#" and are dropped by the template's chapterUrl != '#' guard. This PR lists them, labels the two hidden states differently, and makes opening one produce an honest, state-specific error instead of a blank chapter.

New opt-in source option listPremiumChapters in plugins/multisrc/madara/template.ts, enabled only for tangerinearchive in sources.json (plugin version 2.2.1). Every new code path is inside if (this.options?.listPremiumChapters), so other Madara sources take the exact previous path (two unrelated sources re-checked below).

Chapter list

Premium rows are listed with a reconstructed real URL (/series/<slug>/chapter-<n>/, still server-gated) and a label that distinguishes what the site actually shows:

Site row Label Meaning
TBA release text [Premium - 10 coins] coin-locked now, no free-unlock date announced
Unlocked in 3 weeks release text [Soon - unlocks in 3 weeks] coin-locked now, free-unlock date announced

Example: Chapter 212 [Premium - 10 coins], Chapter 161 [Soon - unlocks in 1 day]. releaseTime is null for these rows (the countdown/TBA is not a release timestamp, and the template's parseData would otherwise turn it into a bogus past date).

URL reconstruction: first number in the anchor text, . → - (Chapter 27.2 → chapter-27-2), plus the token directly after the number when it starts with a non-ASCII character (Chapter 176 🔞 → chapter-176-%F0%9F%94%9E); " - Title" suffixes are not part of the slug (Chapter 250 - (End of the Side Stories) → chapter-250/).

Opening a locked chapter

parseChapter throws instead of returning '':

  • scheduled: Chapter 161 is a scheduled premium chapter: the site unlocks it for free in 1 day, and it is coin-locked (10 coins) until then. LNReader cannot bypass the site's coin gate - open it on https://tangerinearchive.com/ to unlock it with coins.
  • TBA/coin-locked: Chapter 212 is a locked premium chapter (10 coins): its free-unlock date is TBA, and LNReader cannot bypass the site's coin gate - open it on https://tangerinearchive.com/ to unlock it with coins.
  • reconstructed URL that no longer resolves: This chapter could not be found on the site (the URL listed for it may be out of date).

The locked page's markup is identical for both states (the countdown only exists in the chapter list), so the state is read from the series' ajax/chapters/ row found by the page's own #wp-manga-current-chap[data-id]; if that lookup fails the message falls back to the coin-locked wording. A row that is not marked premium in the list but whose page is served locked (a live edge case on a sibling source) still throws, because the check is done on the fetched page, scoped to .reading-content (the div.nav-next ... premium-block block on free pages is outside it).

What was probed (anonymous, read-only)

No login, no cookies, no purchase, and the buy endpoint was never called with parameters. Probing stopped at "the page returns the lock notice".

  • Counts equal the site's own list for 5 series: garden-of-may 212 = 160 free + 52 premium, villain-let-me-touch-you 191 = 145 + 46, holy-night 464 = 359 + 105, in-the-doghouse 251 = 243 + 8, it-seems-like-the-infamous-trash-can-is-right-here 264 = 230 + 34.
  • Reconstructed URLs verified by identity, not just HTTP 200: fetched pages' #wp-manga-current-chap[data-id] equals the row's own data-chapter-<id>, and the body is the lock notice (TBA row, countdown row, emoji slug, title-suffixed row, duplicate number, - 🔞 row).
  • Slug rule validated against ground truth: applying it to the 3289 free rows (whose real hrefs the site supplies) reproduces 3271 exactly; the other 18 differ only by percent-encoding of an emoji that is genuinely part of the slug.
  • Free chapters unchanged: garden-of-may/chapter-160/ and villain/chapter-144/ still return 6538 / 7859 chars.
  • Also verified: the known duplicate-number collision (villain Chapter 166 and Chapter 166 - Secret both reduce to /chapter-166/, which the site serves as the id-16700 Chapter 166 - Secret row) still produces the accurate message for that page, and an unreachable reconstructed URL (/chapter-166-2/, which the site answers with the series page and HTTP 200) produces the "could not be found" error instead of the series summary being returned as a chapter body.

Evidence: parseNovel output cross-checked against an independent parse of POST /series/<slug>/ajax/chapters/ for the 5 series above (counts, per-row labels, reconstructed path set), 6 reconstructed URLs fetched and id-matched, and 5 parseChapter cases (2 free, 3 locked states + the soft-404 case).

Anonymous retrieval sweep

Before accepting "the body is not obtainable anonymously", every legitimate render/API avenue was swept
anonymously and read-only for a known locked chapter (no login, no cookies, no nonce replay, never the buy
endpoint): AMP (/amp/, ?amp, ?amp=1), print (/print/, ?print=1), feeds (/feed/,
?post_type=wp-manga-chapter, series feed), oEmbed, ?_format=json|amp, ?preview, ?_escaped_fragment_=,
cache/CDN variants (?nocache, ?cb, ?ver, compressed, HTTP/1.0), the full wp-json route index (no
chapter route; no chapter CPT in /wp/v2/types; /wp/v2/search empty), JSON-LD and meta descriptions (series
summary only, no articleBody), all 8 paginated chapter sitemaps (3286 URLs, 0 of the 784 known premium
paths
), and the manga_get_reading_nav / manga_get_chapters ajax families across volume/style/type
combinations (premium options always data-redirect="#", no content). The theme's own reading-ajax path needs
data-navigation attributes this site does not render, and the coin plugin's JS is modal-only UI. Every
variant returns the identical gated page, so the states listed by this PR are the honest maximum.

Tests run

  • npm run build:compile — pass
  • npm run build:multisrc — pass
  • npm run check:plugin -- 'plugins/english/TangerineArchive[madara].ts' — PASS (popularNovels 12, searchNovels, parseNovel 173 chapters, parseChapter 14497 chars)
  • Shared-template guard (flag off): SleepyTranslations[madara] PASS and Lovelyblossoms[madara] PASS
  • npx eslint plugins/multisrc/madara/template.ts — clean; npx prettier --check on the changed files — clean

Not tested

  • No entitled-session test. There is no login or purchase anywhere in this change, and the plugin cannot perform a site login; the chapter bodies behind the coin gate are not readable anonymously (server-side gate, independently confirmed). The messages therefore describe the lock rather than showing prose, and the labels never claim readable content.
  • The reader app's rendering of a thrown parseChapter error (only the plugin-level message is verified).
  • The pre-existing releaseTime: 'LL' bug for free chapters (dayjs().format('LL') without localizedFormat) is untouched — separate repo-wide issue.

Authored with AI assistance (pi coding agent) on branch fm/tangerine-premium-tba-split, commit 902c5e4.

…labels

Tangerine Archive publishes coin-locked (premium) chapters with href="#",
so the chapter list dropped them entirely. Add an opt-in
`listPremiumChapters` source option, enabled only for tangerinearchive,
which lists those rows with reconstructed /series/<slug>/chapter-<n>/ URLs
and labels them by the state the site exposes:

- `[Soon - unlocks in <n>]` when the site announces a free-unlock countdown;
- `[Premium - <n> coins]` when the chapter is coin-locked and its free
  unlock date is TBA.

parseChapter now throws a clear, state-specific message for locked chapters
(read from the series ajax row via the page's own chapter post id) and for
reconstructed URLs that no longer resolve, instead of returning an empty
chapter. Free chapters are unchanged, and every new code path is gated on
the option so other Madara sources keep the previous behaviour. Tangerine
Archive 2.2.1.

Co-authored-by: firstmate-crewmate <crewmate@firstmate.local>
@RibatTRW RibatTRW closed this Sep 23, 2026
@RibatTRW
RibatTRW deleted the fm/tangerine-premium-tba-split 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