Skip to content

fix(xiaohongshu): hook dashboard fetch to capture signed datacenter/note/* responses#1732

Open
Benjamin-eecs wants to merge 4 commits into
jackwener:mainfrom
Benjamin-eecs:fix/xhs-creator-detail-signed-api
Open

fix(xiaohongshu): hook dashboard fetch to capture signed datacenter/note/* responses#1732
Benjamin-eecs wants to merge 4 commits into
jackwener:mainfrom
Benjamin-eecs:fix/xhs-creator-detail-signed-api

Conversation

@Benjamin-eecs
Copy link
Copy Markdown
Contributor

@Benjamin-eecs Benjamin-eecs commented May 23, 2026

Description

The four /api/galaxy/creator/datacenter/note/* endpoints behind the creator-note-detail view require an x-s / x-t / x-s-common signing interceptor that the dashboard's own JS installs at page load. The previous in-page roundtrip called fetch() directly from page.evaluate, which bypasses the interceptor and gets HTTP 406, so 观看来源 / 观众画像 / 趋势数据 rows silently never landed even though the help string promised them.

Instead of forging signatures, install a fetch + XHR capture hook on window.__xhsCapture, SPA-navigate to /statistics/note-detail via history.pushState + popstate (a hard page.goto would wipe the hook before the first auto-fetch fires), and harvest the dashboard's own signed responses out of the capture buffer.

Also fix a 1-character endpoint name: /note/audience is renamed to /note/audience/source. The old path returned 404 even when signed; the page actually fetches /note/audience/source for the 观看来源 panel. Confirmed against the live dashboard XHR list while logged in.

Tests updated to mock the new install-hook + SPA-nav + poll-capture sequence at page.evaluate (the previous burst-wait-between-fetches assertion no longer applies).

Related issue: Closes #1728. Reporter diagnosis: @ppop123 traced the signing bypass + endpoint typo and verified the hook + SPA-nav workaround on 86 notes.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🌐 New site adapter
  • 📝 Documentation
  • ♻️ Refactor
  • 🔧 CI / build / tooling

Checklist

  • I ran the checks relevant to this PR
  • I updated tests or docs if needed
  • I included output or screenshots when useful

Documentation (if adding/modifying an adapter)

  • Added doc page under docs/adapters/ (if new adapter)
  • Updated docs/adapters/index.md table (if new adapter)
  • Updated sidebar in docs/.vitepress/config.mts (if new adapter)
  • Updated README.md / README.zh-CN.md when command discoverability changed
  • Used positional args for the command's primary subject unless a named flag is clearly better
  • Normalized expected adapter failures to CliError subclasses instead of raw Error

Screenshots / Output

Mechanism verification (broader XHR hook installed on the live dashboard for diagnosis, on a published test note):

fetch / XHR captured after history.pushState('/statistics/note-detail?noteId=...') + dispatchEvent(popstate):
- xhr https://creator.xiaohongshu.com/api/galaxy/creator/datacenter/note/base?note_id=...
- xhr https://creator.xiaohongshu.com/api/galaxy/creator/datacenter/note/analyze/audience/trend?note_id=...
- xhr https://creator.xiaohongshu.com/api/galaxy/creator/datacenter/note/audience/source?note_id=...

All 4 signed XHRs fire and the hook captures their responses; the typo-fixed /note/audience/source matches what the dashboard actually serves.

End-to-end on a freshly published test note (id 6a1195a8..., 0 views so most metrics return placeholders):

$ opencli xiaohongshu creator-note-detail 6a1195a8000000000702f181
- section: 笔记信息       metric: note_id        value: 6a1195a8000000000702f181
- section: 笔记信息       metric: title          value: 'OpenCLI #1728 fix verification ...'
- section: 笔记信息       metric: published_at   value: 2026-05-23 20:55
- section: 基础数据       metric: 曝光数         value: '-'   extra: '-'
- section: 基础数据       metric: 观看数         value: '0'   extra: '-'
- section: 互动数据       metric: 点赞数         value: '0'   extra: '-'
- section: 互动数据       metric: 评论数         value: '0'   extra: '-'
- section: 互动数据       metric: 收藏数         value: '0'   extra: '-'
- section: 互动数据       metric: 分享数         value: '0'   extra: '-'
- section: 趋势说明       metric: 观众趋势       value: 暂不可用   extra: 观看数不足100,暂时无法分析

The 趋势说明 row at the bottom is the diagnostic signal: it is produced by appendTrendRows against a non-null apiPayload, so the API response was captured and parsed. Without this fix, apiPayload === null (silenced 406) and that section was completely absent.

Note: the trend / audience source / audience portrait sections still need ≥100 views to surface real breakdown rows (xhs platform rule), which @ppop123 verified end-to-end on 86-148 note accounts in #1728. The mechanism is unblocked here; the dataset depth depends on the target account.

@Benjamin-eecs Benjamin-eecs marked this pull request as ready for review May 23, 2026 12:04
Copilot AI review requested due to automatic review settings May 23, 2026 12:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes missing audience-source / audience-portrait / trend sections in the xiaohongshu creator-note-detail adapter by capturing the creator dashboard’s own signed /api/galaxy/creator/datacenter/note/* responses (instead of issuing unsigned fetch() calls from page.evaluate), and corrects an endpoint suffix typo.

Changes:

  • Update the audience source endpoint suffix to /api/galaxy/creator/datacenter/note/audience/source.
  • Install a window.fetch + XMLHttpRequest capture hook and SPA-navigate via history.pushState + popstate to harvest signed dashboard responses.
  • Update tests to mock the new hook + SPA-nav + polling capture flow.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
clis/xiaohongshu/creator-note-detail.js Switch from direct API fetch() to capture-hook + SPA navigation; fix the audience source endpoint suffix.
clis/xiaohongshu/creator-note-detail.test.js Update mocks/assertions to reflect hook installation and capture-buffer polling behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +255 to +258
await page.evaluate(`(() => {
if (window.__xhsCapture) return;
window.__xhsCapture = {};
const origFetch = window.fetch;
Comment on lines +289 to +290
HookedXHR.prototype = OrigXHR.prototype;
window.XMLHttpRequest = HookedXHR;
…ote/* responses

The four /api/galaxy/creator/datacenter/note/* endpoints behind the
creator-note-detail view require an x-s / x-t / x-s-common signing
interceptor that the dashboard's own JS installs at page load. The
previous in-page roundtrip called fetch() directly from page.evaluate,
which bypasses the interceptor and gets HTTP 406, so 观看来源 / 观众画像 /
趋势数据 rows silently never landed even though the help string promised
them.

Instead of forging signatures, install a fetch + XHR capture hook on
window.__xhsCapture, SPA-navigate to /statistics/note-detail via
history.pushState + popstate (a hard page.goto would wipe the hook
before the first auto-fetch fires), and harvest the dashboard's own
signed responses out of the capture buffer.

Also fix a 1-character endpoint name: /note/audience -> /note/audience/source.
The old path returned 404 even when signed; the page actually fetches
/note/audience/source for the 观看来源 panel. Confirmed against the live
dashboard XHR list while logged in.

Tests updated to mock the new install-hook + SPA-nav + poll-capture
sequence at page.evaluate (the previous burst-wait-between-fetches
assertion no longer applies).

Closes jackwener#1728.

Reporter diagnosis: @ppop123 traced the signing bypass + endpoint typo
and verified the hook + SPA-nav workaround on 86 notes.
…ibling tone

Sibling helper functions in creator-note-detail.js have no doc-comment
block above the declaration; the 5-line WHY block on the new hook was
out of style. Compress to two lines covering the same WHY (signed API
bypass + 406) and let the rest of the context live in the commit body
of the parent fix.
Inline literals (20 iteration cap, 0.5s wait) drift from sibling
convention in clis/xiaohongshu/delete-note.js where the same kind of
post-write polling is named VERIFY_TIMEOUT_MS / VERIFY_POLL_MS. Promote
the two values to CAPTURE_POLL_ATTEMPTS / CAPTURE_POLL_INTERVAL_S so
the loop reads against an explicit budget and future tuning lands in
one place.
@Benjamin-eecs Benjamin-eecs force-pushed the fix/xhs-creator-detail-signed-api branch from 52bf2c4 to f636191 Compare May 23, 2026 16:16
Two polish items from the Copilot review on jackwener#1732:

- Buffer reset: window.__xhsCapture is now cleared on every install call
  so stale captures from a previous run on the same tab cannot leak into
  the current navigation's harvest. The wrapper-install guard moves to a
  separate __xhsCaptureInstalled flag so the fetch/XHR monkey-patches
  themselves are still installed exactly once per page lifetime.
- XHR static constants: HookedXHR now copies the readyState constants
  (UNSENT / OPENED / HEADERS_RECEIVED / LOADING / DONE) from the original
  constructor so dashboard code that reads XMLHttpRequest.DONE etc against
  the constructor keeps working.
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.

[Bug] xiaohongshu creator-note-detail returns only 3 sections — 观看来源/观众画像/趋势数据 missing despite help string promising them

2 participants