333 lines
12 KiB
Markdown
333 lines
12 KiB
Markdown
---
|
|
name: xiaohongshu-reply-notifications
|
|
description: "Automatically handle unreplied XiaoHongShu (小红书) items in 通知-评论和@ by opening each note detail, collecting full comment context, conditionally liking high-quality comments, and posting contextual replies with strict login gating and online reply-state checks."
|
|
---
|
|
|
|
# Xiaohongshu Reply Notifications
|
|
|
|
## Workflow
|
|
|
|
1. Confirm login state and account identity.
|
|
2. Open `通知` and switch to `评论和@`.
|
|
3. Discover all candidate cards and loop through them (newest to oldest).
|
|
4. Enter each note detail page and determine whether the target is unreplied.
|
|
5. Evaluate comment quality and conditionally like the target comment.
|
|
6. Generate contextual reply text with accuracy-confidence gate.
|
|
7. Verify online replied-state for target thread and apply runtime dedupe.
|
|
8. Execute reply according to reply mode.
|
|
9. Verify result and continue until candidates are exhausted or early-exit triggers.
|
|
10. Return compact batch report and per-item details.
|
|
|
|
## Shared Session Policy
|
|
|
|
- Use a per-thread Playwright CLI session:
|
|
- Auto session policy: `tools/pw` derives one Playwright session per `CODEX_THREAD_ID` (fallback: `PLAYWRIGHT_SESSION_OWNER` or explicit `--session`).
|
|
- Use `/Users/xd/java/xhs/tools/pw` as the only browser entrypoint.
|
|
- Prefer the default auto session; pass `--session <name>` only when explicit isolation is required.
|
|
|
|
## Network Preflight Gate (Required)
|
|
|
|
- Before running this flow, run:
|
|
- `tools/pw --help`
|
|
- If it fails with network sandbox signatures (`ENOTFOUND`, `EAI_AGAIN`, `ETIMEDOUT`, `ECONNREFUSED`, or `registry.npmjs.org` fetch failure):
|
|
- rerun the same command with escalation (`sandbox_permissions=require_escalated`).
|
|
- Do not continue the notification-reply flow until preflight succeeds.
|
|
|
|
Quick run:
|
|
|
|
```bash
|
|
python3 scripts/run_reply_notifications.py \
|
|
--mode safe \
|
|
--max-items 10
|
|
```
|
|
|
|
Batch live run:
|
|
|
|
```bash
|
|
python3 scripts/run_reply_notifications.py \
|
|
--mode live \
|
|
--max-items 20 \
|
|
--early-stop-non-actionable 20
|
|
```
|
|
|
|
## Reply-State Truth Source
|
|
|
|
- Use online thread state as source of truth:
|
|
- check target `parent_comment_id` in note detail
|
|
- expand replies with limits
|
|
- if your own account reply is visible, mark `already_replied`
|
|
- Do not rely on local persisted “already handled” files.
|
|
- Runtime dedupe is memory-only for current run:
|
|
- `target_key = note_id|parent_comment_id`
|
|
- `dedupe_key = note_id|parent_comment_id|reply_hash`
|
|
- only prevents duplicate sends in the same process run.
|
|
|
|
## 1) Login Gate (Required)
|
|
|
|
- Open `https://www.xiaohongshu.com` automatically.
|
|
- Confirm login by both signals:
|
|
- left sidebar has `我`
|
|
- `我` links to `/user/profile/...`
|
|
- If not logged in:
|
|
- stop immediately
|
|
- tell user to log in first
|
|
- do not continue any reply actions in this turn.
|
|
|
|
## 2) Enter Notification Scope
|
|
|
|
- Open `https://www.xiaohongshu.com/notification` (or click left sidebar `通知`).
|
|
- Confirm URL contains `/notification`.
|
|
- Click `评论和@` tab.
|
|
- Never operate in `赞和收藏` or `新增关注` for this skill.
|
|
|
|
## 3) Discover Candidates (All Unreplied Flow)
|
|
|
|
Goal: process all unreplied items in `评论和@`.
|
|
|
|
Loop behavior:
|
|
|
|
- Scroll notification list to load more cards.
|
|
- Continue while new actionable cards are still discovered.
|
|
- Stop when no new cards appear in two consecutive scroll rounds.
|
|
- Safety cap:
|
|
- `max_scan_cards = 300` per run (to avoid endless scrolling).
|
|
|
|
Early-exit optimization (newest -> oldest list):
|
|
|
|
- Because `评论和@` is ordered from new to old, allow early stop when old non-actionable cards dominate.
|
|
- Maintain `consecutive_old_non_actionable` counter; increment when a card is any of:
|
|
- `already_replied`
|
|
- `skipped_duplicate` (runtime dedupe hit)
|
|
- older than run time window and not actionable
|
|
- Reset this counter to `0` whenever an actionable unreplied card is found.
|
|
- Trigger early exit when:
|
|
- `consecutive_old_non_actionable >= 20`, or
|
|
- one full scroll round yields zero actionable cards and all seen cards in that round are old non-actionable cards.
|
|
- Always prefer correctness:
|
|
- if card time/order looks ambiguous, do not early-exit; continue scanning.
|
|
|
|
For each discovered card, extract:
|
|
|
|
- notifier nickname
|
|
- event type: `评论了你的笔记` / `回复了你的评论` / `在评论中@了你`
|
|
- notifier text snippet
|
|
- your original-comment snippet if shown on card
|
|
- card timestamp text
|
|
- card thumbnail/content entry for opening note detail.
|
|
|
|
## 4) Enter Note Detail And Collect Context (Required Before Reply)
|
|
|
|
For each candidate card:
|
|
|
|
- Open the related note detail from notification card thumbnail/content.
|
|
- Collect note context:
|
|
- note title/main text
|
|
- hashtags
|
|
- note author nickname
|
|
- Collect thread context from detail comments:
|
|
- target commenter nickname and comment text
|
|
- target comment timestamp (if visible)
|
|
- If event is `回复了你的评论`:
|
|
- locate **your original comment** in the detail thread before generating reply.
|
|
- Prefer match by current account profile id from sidebar `我 -> /user/profile/...`.
|
|
- Fallback: match with the original-comment snippet shown in notification card.
|
|
- If event is `在评论中@了你`:
|
|
- locate the mentioning comment in detail thread and capture mention sentence.
|
|
- Extract target identifiers:
|
|
- `note_id`
|
|
- `parent_comment_id` (the comment being replied to)
|
|
|
|
Unreplied decision (required):
|
|
|
|
- Determine whether current account already replied to this target comment thread.
|
|
- If already replied:
|
|
- mark `already_replied`
|
|
- skip send for this card.
|
|
- If not replied:
|
|
- continue normal flow.
|
|
|
|
Hard rule:
|
|
|
|
- Do not directly reply from notification-list `回复` when detail context is missing.
|
|
- Reply should be made after detail context is confirmed.
|
|
|
|
## 5) Comment Quality And Conditional Like
|
|
|
|
After detail context is confirmed, evaluate the target comment quality.
|
|
|
|
Quality is considered `satisfied` when comment is at least one of:
|
|
|
|
- specific and relevant to the note topic
|
|
- constructive question or useful follow-up
|
|
- polite and non-spam interaction
|
|
|
|
Quality is `not_satisfied` when comment is primarily:
|
|
|
|
- meaningless spam / repeated emojis only
|
|
- obvious ad diversion
|
|
- abusive or provocative without useful content
|
|
|
|
Like rule (target comment only):
|
|
|
|
- if quality is `satisfied`, click like (`赞`) on the target comment row once.
|
|
- if already liked, keep current state and do not toggle off.
|
|
- if quality is `not_satisfied`, skip like.
|
|
|
|
Like is independent from reply mode:
|
|
|
|
- `safe_mode` and `live_mode` can both perform this conditional like action.
|
|
|
|
## 6) Reply Drafting Rules
|
|
|
|
- Use short, natural Chinese.
|
|
- Prefer one sentence, <= 35 Chinese characters.
|
|
- Must be specific to note/comment context, avoid template spam.
|
|
- If asked a concrete question and confidence is high, answer directly.
|
|
- If context is unclear, ask one short clarifying follow-up.
|
|
- Skip and mark `needs_manual_review` for toxic/abusive/high-risk content.
|
|
|
|
Accuracy-confidence gate (required):
|
|
|
|
- Before finalizing each reply, assign confidence:
|
|
- `high`: question intent is clear and answer can be supported by current note/thread context.
|
|
- `medium`: intent is mostly clear, but some details are uncertain.
|
|
- `low`: key context is missing/ambiguous, likely to answer inaccurately.
|
|
- Default principle:
|
|
- if you can answer clearly, answer directly (do not overuse DM handoff).
|
|
- If confidence is `medium`:
|
|
- provide a cautious but useful direct answer, avoid absolute claims.
|
|
- If confidence is `low`:
|
|
- prefer private-message handoff reply inviting user to DM you.
|
|
|
|
DM handoff style:
|
|
|
|
- concise, polite, no over-commitment
|
|
- explicitly state you may explain more accurately in private chat
|
|
- example templates:
|
|
- `这个细节怕说不准,方便私信我,我给你详细说下。`
|
|
- `这块情况有点细,私信我一下,我一对一给你回复更准确。`
|
|
- `我先不乱说,私信我我按你的情况详细答你。`
|
|
|
|
## 7) Online Replied-State Check And Runtime Dedupe
|
|
|
|
Before final send in `live_mode`, compute:
|
|
|
|
- `note_id`: from note detail URL or note container attribute.
|
|
- `parent_comment_id`: the comment being replied to (from comment row context/network metadata).
|
|
- `reply_hash`: SHA-256 of normalized reply text.
|
|
|
|
Normalization for `reply_hash`:
|
|
|
|
- trim leading/trailing spaces
|
|
- collapse repeated internal spaces
|
|
- normalize full-width/half-width punctuation when possible
|
|
- remove leading `回复 xxx :` prefix if present
|
|
|
|
Key format (runtime):
|
|
|
|
- `target_key = note_id + \"|\" + parent_comment_id`
|
|
- `dedupe_key = note_id + \"|\" + parent_comment_id + \"|\" + reply_hash`
|
|
|
|
Online check rule (required):
|
|
|
|
- For target `parent_comment_id`, inspect thread replies in detail page.
|
|
- If not enough replies are visible, click expand controls:
|
|
- `展开` / `更多` / `查看回复` / `全部回复` (with limits).
|
|
- Limits:
|
|
- `max_expand_rounds` (default `8`)
|
|
- `max_scan_replies` (default `200`)
|
|
- `max_expand_seconds` (default `20`)
|
|
- Decision:
|
|
- `already_replied`: my reply is visible in thread -> skip send.
|
|
- `not_replied`: checked within limits and no my reply found -> continue.
|
|
- `unknown`: cannot determine confidently -> mark `needs_manual_review`, skip auto-send.
|
|
|
|
Runtime dedupe rule:
|
|
|
|
- In current run only:
|
|
- if `target_key` or `dedupe_key` already seen, mark `skipped_duplicate`.
|
|
- otherwise continue.
|
|
|
|
## 8) Reply Execution
|
|
|
|
- In note detail comments, click the target comment row's `回复`.
|
|
- Confirm bottom editor shows `回复 <nickname>` and quoted context.
|
|
- Type the generated reply.
|
|
- Reply mode:
|
|
- `safe_mode` (default): do not click final `发送`; keep as draft/plan only.
|
|
- `live_mode`: click `发送`.
|
|
- When user request is explicit batch intent such as `自动回复所有未回复`, treat this as `live_mode`.
|
|
|
|
## 9) Verify Success (live_mode)
|
|
|
|
At least one must be observed:
|
|
|
|
- `评论成功` toast appears, or
|
|
- new reply appears in thread, or
|
|
- editor exits reply state and send button resets.
|
|
|
|
If verification fails:
|
|
|
|
- mark this item as `sent_unknown`
|
|
- do not auto-retry automatically in current run.
|
|
|
|
## 10) Reliability And Recovery
|
|
|
|
- Re-snapshot after every major step:
|
|
- tab switch
|
|
- note-detail open/close
|
|
- reply-mode change
|
|
- On click interception/stale refs:
|
|
- wait briefly
|
|
- snapshot again
|
|
- retry once
|
|
- If detail overlay blocks notification operations:
|
|
- close detail first, then continue next card.
|
|
|
|
## 11) Return Report (Batch)
|
|
|
|
Return compact structured results per processed item:
|
|
|
|
- note url/title
|
|
- note_id
|
|
- parent_comment_id
|
|
- dedupe_key
|
|
- event type
|
|
- notifier nickname
|
|
- notifier comment
|
|
- your original comment (if applicable)
|
|
- comment_quality (`satisfied` or `not_satisfied`)
|
|
- liked (`yes`, `no`, `already_liked`, `like_failed`)
|
|
- generated reply text
|
|
- mode (`safe_mode` or `live_mode`)
|
|
- status (`replied`, `already_replied`, `planned`, `skipped_duplicate`, `needs_manual_review`, `sent_unknown`, `failed`)
|
|
- failure reason if any
|
|
|
|
Return batch summary:
|
|
|
|
- total cards scanned
|
|
- actionable cards
|
|
- unreplied cards detected
|
|
- replies sent
|
|
- already-replied skipped
|
|
- duplicate skipped
|
|
- failed count
|
|
|
|
## Boundaries
|
|
|
|
- Do not send private messages, follow/unfollow, or modify account settings.
|
|
- Do not like unrelated comments; only like the current target comment when quality is `satisfied`.
|
|
- Do not mass-reply with repeated text.
|
|
- In `safe_mode`, never click final send.
|
|
- Stop and notify user if login state is lost or safety/risk prompts appear.
|
|
- Do not bypass online replied-state check when state is `unknown`.
|
|
|
|
## Script
|
|
|
|
- `scripts/run_reply_notifications.py`
|
|
- End-to-end runner with strict login gate, `评论和@` scanning, note-detail context extraction, online replied-state checks with expand limits, runtime dedupe, conditional like, and safe/live reply modes.
|
|
- Exit codes:
|
|
- `0`: flow completed (including no-actionable cases)
|
|
- `1`: execution failure
|
|
- `2`: not logged in (requires user login and rerun)
|