Files
jackwener__opencli/docs/adapters/browser/rednote.md
Benjamin Liu 64ac362a40 feat(rednote): add rednote.com adapter mirroring xiaohongshu read commands (#1136) (#1475)
* feat(rednote): add rednote.com adapter mirroring xiaohongshu read commands (#1136)

Implements rednote.com support as discussed in issue #1136. The mainland
xiaohongshu adapter stays in place; international users redirected to
www.rednote.com now have a CLI without a copy-pasted adapter.

Issue #1136 documents that xiaohongshu and rednote share DOM selectors,
URL paths, API paths, response schema, cookies, and the xsec_token auth
mechanism. The only material differences:

  Layer            xiaohongshu                rednote
  Web host         www.xiaohongshu.com        www.rednote.com
  API host         edith.xiaohongshu.com      webapi.rednote.com
  Security host    fe-static.xhscdn.com       as.rednote.com
  Cookie root      .xiaohongshu.com           .rednote.com
  Search gate      Inline text                Full-screen modal + text

## Architecture (minimal)

`clis/xiaohongshu/*` keep all selector / regex / extraction logic. Each
command file is touched minimally to export the IIFE or pipeline so the
sibling adapter can reuse it:

  search.js          + export const buildSearchExtractJs(webHost)
                     + export const command = cli({...})
  note.js            + export const NOTE_EXTRACT_JS
                     + export const command = cli({...})
  comments.js        + export function buildCommentsExtractJs(withReplies)
                     + export parseCommentLimit
                     + export const command = cli({...})
  download.js        + export function buildDownloadExtractJs(noteId)
                       (CDN allowlist now includes rednote alongside xhscdn)
                     + export const command = cli({...})
  user.js            + export const USER_SNAPSHOT_JS
                     + export const command = cli({...})
  feed.js            + export function buildFeedPipeline(webHost)
                     + export const command = cli({...})
  notifications.js   + export function buildNotificationsPipeline(webHost)
                     + export const command = cli({...})
  note-helpers.js    buildNoteUrl now accepts `cookieRoot` + `signedUrlHint`
                     options (defaults preserved so xhs callers and tests
                     are unchanged)
  user-helpers.js    buildXhsNoteUrl / extractXhsUserNotes accept an
                     optional `webHost` argument (default xhs)

The `export const command = cli({...})` pattern matches twitter/lists.js
and clis/discord-app/*; without it the build-manifest scanner attributes
xhs's command to whichever rednote sibling triggered the transitive
import first.

## clis/rednote/ — thin shims

Each rednote command file imports the relevant builder / constant from
its xiaohongshu sibling and calls `cli()` with the rednote host triple.
No selectors, regexes, or extraction logic are duplicated.

  search.js          imports buildSearchExtractJs + noteIdToDate
                     declares its own WAIT_FOR_CONTENT_JS (modal + text
                     login-gate variants — the one xhs behaviour that
                     genuinely differs)
  note.js            imports NOTE_EXTRACT_JS + buildNoteUrl + parseNoteId
  comments.js        imports buildCommentsExtractJs + parseCommentLimit
                     + buildNoteUrl + parseNoteId
  download.js        imports buildDownloadExtractJs + buildNoteUrl + parseNoteId
  user.js            imports USER_SNAPSHOT_JS + extractXhsUserNotes
                     + normalizeXhsUserId

## Scope (initial)

Ships the five commands verified live against the user's logged-in
rednote.com session: search / note / comments / user / download.

`feed` and `notifications` are intentionally left out. Both rely on
intercepting the xiaohongshu Pinia store at the `homefeed` / `you`
capture pattern; live verification on rednote returns `tap → dict
(error)` for the feed step, so shipping them would surface a broken
contract. The mainland xiaohongshu commands continue to work. Adding
the rednote-side feed / notifications is straightforward follow-up
work once someone with rednote access maps the network surface.

Creator-center commands (publish, creator-*) have no rednote
counterpart and stay xiaohongshu-only, per the reporter's note in #1136.

## Verification

  - clis/xiaohongshu/ + clis/rednote/: 103/103 tests green
  - npx tsc --noEmit: clean
  - npm run build: 807 manifest entries (xhs 13 + rednote 5 + everything
    else preserved)
  - silent-column-drop / typed-error-lint: 103 / 189 baseline entries,
    no new violations
  - Live verify against the user's rednote.com session:
      rednote search "travel" --limit 1 → real note row
      rednote note <signed-url>         → 7 field/value rows
      rednote comments <signed-url> --limit 3 → 3 top-level rows
      rednote user 5b21f6564eacab3b38f05c39 --limit 2 → 2 profile notes
    Spaced 15–30s between runs per the xhs/rednote rate-limit guidance;
    no write commands invoked. Regression check: xiaohongshu/feed on
    the existing mainland session still returns the standard 6-field
    rows after the refactor.

Closes #1136

* fix(rednote): tighten adapter failure boundaries

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-05-12 02:32:25 +08:00

3.5 KiB

Rednote (国际版小红书)

Mode: 🔐 Browser · Domain: www.rednote.com (cookie root .rednote.com)

Rednote is the international mirror of Xiaohongshu. Logged-in users outside mainland China are redirected from www.xiaohongshu.com to www.rednote.com, where the codebase is largely the same but hostnames and cookie root differ. This adapter exists so those users have working CLI commands without juggling the mainland-domain xiaohongshu adapter. See issue #1136 for the host-by-host comparison.

Commands

Command Description
opencli rednote search Search notes by keyword (returns title, author, likes, URL, author URL)
opencli rednote note Read full note content (title, author, description, likes, collects, comments, tags)
opencli rednote comments Read comments from a note (--with-replies for nested 楼中楼 replies)
opencli rednote user Get public notes from a user profile
opencli rednote feed Home feed (reads the hydrated Pinia store)
opencli rednote notifications Notifications (`--type mentions
opencli rednote download Download images and videos from a note

Usage Examples

# Search for notes
opencli rednote search travel --limit 10

# Read a note's full content (pass URL from search results to preserve xsec_token)
opencli rednote note "https://www.rednote.com/search_result/<id>?xsec_token=..."

# Read comments with nested replies (楼中楼)
opencli rednote comments "https://www.rednote.com/search_result/<id>?xsec_token=..." --with-replies --limit 20

# JSON output
opencli rednote search travel -f json

# User profile notes
opencli rednote user 5b21f6564eacab3b38f05c39 --limit 10

# Download
opencli rednote download "https://www.rednote.com/search_result/<id>?xsec_token=..."

Note: note, comments, and download require a full signed rednote.com note URL with xsec_token. Bare note IDs and xhslink.com short links are not accepted because they cannot prove the rednote host/cookie identity before navigation.

Prerequisites

Implementation

The rednote command files are thin shims that import the DOM-extraction IIFEs and URL helpers from their clis/xiaohongshu/* counterparts and call cli() with the rednote host triple. There is no duplicate copy of selectors, regexes, or extraction logic.

Layer xiaohongshu rednote
Web host www.xiaohongshu.com www.rednote.com
API host edith.xiaohongshu.com webapi.rednote.com
Security/signing host fe-static.xhscdn.com as.rednote.com
Cookie root .xiaohongshu.com .rednote.com
Search login gate Inline 登录后查看搜索结果 text Full-screen login modal (plus the inline text as a fallback)

rednote feed and rednote notifications cannot share the xiaohongshu intercept pipeline. rednote's feed.feeds is populated from SSR and the field names are camelCase (noteCard.displayTitle, interactInfo.likedCount) rather than xhs's snake_case API shape, so the xhs tap captures nothing. notification.notificationMap[<type>].messageList is filled by the in-page getNotification() action without firing a network request the tap can match. Both rednote commands therefore read the hydrated Pinia store in func mode, accepting both casing conventions when extracting fields. Creator-center commands (publish, creator-*) have no rednote counterpart and stay xiaohongshu-only.