fix(xiaohongshu): hook dashboard fetch to capture signed datacenter/note/* responses#1732
Open
Benjamin-eecs wants to merge 4 commits into
Open
fix(xiaohongshu): hook dashboard fetch to capture signed datacenter/note/* responses#1732Benjamin-eecs wants to merge 4 commits into
Benjamin-eecs wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
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+XMLHttpRequestcapture hook and SPA-navigate viahistory.pushState+popstateto 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.
52bf2c4 to
f636191
Compare
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.
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The four
/api/galaxy/creator/datacenter/note/*endpoints behind thecreator-note-detailview require anx-s/x-t/x-s-commonsigning interceptor that the dashboard's own JS installs at page load. The previous in-page roundtrip calledfetch()directly frompage.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-detailviahistory.pushState+popstate(a hardpage.gotowould 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/audienceis renamed to/note/audience/source. The old path returned 404 even when signed; the page actually fetches/note/audience/sourcefor 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
Checklist
Documentation (if adding/modifying an adapter)
docs/adapters/(if new adapter)docs/adapters/index.mdtable (if new adapter)docs/.vitepress/config.mts(if new adapter)README.md/README.zh-CN.mdwhen command discoverability changedCliErrorsubclasses instead of rawErrorScreenshots / Output
Mechanism verification (broader XHR hook installed on the live dashboard for diagnosis, on a published test note):
All 4 signed XHRs fire and the hook captures their responses; the typo-fixed
/note/audience/sourcematches what the dashboard actually serves.End-to-end on a freshly published test note (id
6a1195a8..., 0 views so most metrics return placeholders):The
趋势说明row at the bottom is the diagnostic signal: it is produced byappendTrendRowsagainst a non-nullapiPayload, 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.