feat(xiaohongshu): add notification reply skill with online reply-state checks
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
---
|
||||
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)
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "XHS Reply Notifications"
|
||||
short_description: "Auto-reply unreplied 评论和@ notifications with context, likes, and online reply-state checks."
|
||||
default_prompt: "Use $xiaohongshu-reply-notifications with auto per-thread Playwright session via /Users/xd/java/xhs/tools/pw and prefer running python3 scripts/run_reply_notifications.py as the main entrypoint (safe mode by default, live mode only when user explicitly asks to auto-reply all unreplied); execute browser steps only through /Users/xd/java/xhs/tools/pw, enforce strict login gate, scan 通知 > 评论和@ in newest-to-oldest order with early-exit on consecutive old non-actionable items, enter note detail for full context before reply, check online replied-state for each target parent_comment_id with bounded expand rounds before sending, conditionally like high-quality target comments, and apply runtime-only dedupe keys target_key(note_id|parent_comment_id) and dedupe_key(note_id|parent_comment_id|reply_hash)."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
+1475
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user