Skill: shawntribbiani/rockingland-trader-monitor (version 1.0.0)

--- name: xiaohongshu-trader-monitor description: Xiaohongshu (RedNote) user post keyword monitoring and WeChat notification. Use when the user needs to (1) monitor a specific Xiaohongshu user's latest posts for keywords in titles, (2) get notified via WeChat when target keywords appear in new posts, (3) set up automated periodic checks for a Xiaohongshu trader/seller's updates, or (4) configure keyword-based alerts for Xiaohongshu content. Triggers on phrases like "小红书监测", "远行商人", "帖子监控", "关键词提醒", "xiaohongshu monitor". --- # Xiaohongshu Trader Monitor 监测指定小红书用户的最新帖子，标题含关键词时自动推送到微信。 ## ⚠️ 使用前配置（必须） 本 skill 需要你填入自己的认证信息才能运行。打开 `scripts/xhs_trader_monitor.py`，修改**用户配置区**（文件顶部）： ### 1. 微信推送目标 ID 获取你的 openclaw-weixin 用户 ID： ```bash openclaw message list-accounts --channel openclaw-weixin ``` 将 `TARGET_ID` 替换为你的 ID，格式为 `[REDACTED_SECRET]`。 ### 2. 小红书目标用户信息 **USER_ID**：目标用户主页 URL 中的 ID - 格式：`https://www.xiaohongshu.com/user/profile/USER_ID` - 从分享链接或搜索结果中获取 **USER_XSEC**：xsec_token（认证令牌） - 从目标用户任意帖子的 feed 数据中提取 `xsecToken` - 或运行 `mcp-call.sh user_profile` 后从返回数据中获取 ### 3. 监测关键词 在 `KEYWORDS` 列表中配置你要监测的关键词： ```python KEYWORDS = ["棱镜球", "炫彩蛋", "同乘蛋", "祝福项坠"] ``` ### 4. Cookies 登录 将已登录小红书的浏览器 cookies 导出为 JSON 数组，保存到 `cookies.json`： ```json [ {"name": "a1", "value": "...", "domain": ".xiaohongshu.com", "path": "/"}, {"name": "web_session", "value": "...", "domain": ".xiaohongshu.com", "path": "/"} ``` 放置路径（按优先级）： 1. `~/cookies.json` 2. `~/.xiaohongshu/cookies.json` 3. 环境变量 `XHS_COOKIES_SRC` 指定的路径 > ⚠️ 首次登录可通过 `mcp-call.sh get_login_qrcode` 获取二维码扫码登录，但当前环境多被风控，推荐直接导出 cookies。 ## Quick Start ### 安装依赖 ```bash # 1. 安装 xiaohongshu-mcp 二进制 # 从 GitHub Releases 下载对应平台文件： # https://github.com/xpzouying/xiaohongshu-mcp/releases mkdir -p ~/.local/bin mv xiaohongshu-mcp-linux-amd64 ~/.local/bin/xiaohongshu-mcp mv xiaohongshu-login-linux-amd64 ~/.local/bin/xiaohongshu-login chmod +x ~/.local/bin/xiaohongshu-* # 2. 安装系统依赖 apt-get install -y jq xvfb # 3. 检查依赖 ./scripts/install-check.sh ``` ### 配置并运行 1. 编辑 `scripts/xhs_trader_monitor.py` 填入你的配置 2. 运行监测： ```bash cd scripts/ python3 xhs_trader_monitor.py ``` ### 配置定时任务 ``` 5 8,12,16,20 * * * /usr/bin/python3 /path/to/xiaohongshu-trader-monitor/scripts/xhs_trader_monitor.py >> /path/to/logs/xhs_trader.log 2>&1 ``` ## How It Works 1. **启动 MCP**：自动启动 xiaohongshu-mcp 服务（含 Xvfb 虚拟显示） 2. **获取帖子**：调用 `user_profile` 获取目标用户主页最新帖子 3. **关键词匹配**：检查最近 5 条帖子标题是否含配置的关键词 4. **日期过滤**：只检查**今天**发布的帖子（MM.DD 前缀匹配） 5. **去重推送**：已通知过的帖子 ID 不会重复推送 6. **微信通知**：命中关键词时推送消息到微信 7. **关闭 MCP**：监测完成后自动关闭服务 ## Resources ### scripts/ - `xhs_trader_monitor.py` — 核心监测脚本（需配置用户信息） - `start-mcp.sh` — 启动 MCP 服务（自动管理 Xvfb） - `stop-mcp.sh` — 停止 MCP 服务 - `mcp-call.sh` — 通用 MCP 调用脚本 - `install-check.sh` — 依赖检查 ### references/ - `trader_rules.md` — 完整配置指南、故障排查、消息模板 ## Files | File | Purpose | |------|---------| | `scripts/xhs_trader_monitor.py` | Core monitoring script (requires user config) | | `scripts/start-mcp.sh` | Start MCP service with Xvfb | | `scripts/stop-mcp.sh` | Stop MCP service | | `scripts/mcp-call.sh` | Generic MCP tool caller | | `scripts/install-check.sh` | Dependency checker | | `references/trader_rules.md` | Configuration guide and troubleshooting | | `cookies.json` | Login cookies (user-provided, not included) | | `xhs_trader_state.json` | Notification state (auto-generated) |

path: install-check.sh
content: #!/bin/bash
# 检查小红书 MCP 依赖是否已安装

set -e

echo "检查小红书 MCP 依赖..."
echo ""

XHS_MCP="$HOME/.local/bin/xiaohongshu-mcp"
XHS_LOGIN="$HOME/.local/bin/xiaohongshu-login"

check_binary() {
    local name="$1"
    local path="$2"
    if [ -f "$path" ]; then
        echo "✅ $name: $path"
        return 0
    else
        echo "❌ $name: 未找到"
        return 1
    fi
}

MISSING=0

check_binary "xiaohongshu-mcp" "$XHS_MCP" || MISSING=1
check_binary "xiaohongshu-login" "$XHS_LOGIN" || MISSING=1

echo ""

# 检查 jq（必需，用于安全构建 JSON）
if command -v jq &> /dev/null; then
    echo "✅ jq: $(which jq)"
else
    echo "❌ jq: 未安装（必需，用于安全构建 JSON）"
    echo "   安装: apt-get install jq / brew install jq"
    MISSING=1
fi

# 检查 Python3（track-topic.py 需要）
if command -v python3 &> /dev/null; then
    echo "✅ python3: $(python3 --version)"
else
    echo "⚠️  python3: 未安装（热点跟踪功能需要）"
fi

echo ""

if [ $MISSING -eq 1 ]; then
    echo "=========================================="
    echo "缺少必要依赖，请按以下步骤安装："
    echo ""
    echo "1. 从 GitHub Releases 下载对应平台的二进制文件："
    echo "   https://github.com/xpzouying/xiaohongshu-mcp/releases"
    echo ""
    echo "2. 解压并安装到 ~/.local/bin/："
    echo "   mkdir -p ~/.local/bin"
    echo "   mv xiaohongshu-mcp-linux-amd64 ~/.local/bin/xiaohongshu-mcp"
    echo "   mv xiaohongshu-login-linux-amd64 ~/.local/bin/xiaohongshu-login"
    echo "   chmod +x ~/.local/bin/xiaohongshu-*"
    echo ""
    echo "3. 确保 ~/.local/bin 在 PATH 中（可选）"
    echo "=========================================="
    exit 1
else
    echo "✅ 所有依赖已就绪"
fi


path: mcp-call.sh
content: #!/bin/bash
# 通用 MCP 调用脚本（支持 Streamable HTTP + Session ID）

set -e

TOOL_NAME="$1"
TOOL_ARGS="$2"
MCP_URL="${MCP_URL:-http://localhost:18060/mcp}"
export no_proxy="${no_proxy:+$no_proxy,}localhost,127.0.0.1"

# 检查 jq 依赖
if ! command -v jq &> /dev/null; then
    echo "错误: 需要安装 jq (apt-get install jq / brew install jq)"
    exit 1
fi

if [ -z "$TOOL_NAME" ]; then
    echo "用法: $0 <tool_name> [json_args]"
    echo ""
    echo "可用工具:"
    echo "  check_login_status    - 检查登录状态"
    echo "  search_feeds          - 搜索内容 {\"keyword\": \"...\", \"filters\": {\"sort_by\": \"最新\"}}"
    echo "  list_feeds            - 获取首页推荐"
    echo "  get_feed_detail       - 获取帖子详情 {\"feed_id\": \"...\", \"xsec_token\": \"...\"}"
    echo "  post_comment_to_feed  - 发表评论 {\"feed_id\": \"...\", \"xsec_token\": \"...\", \"content\": \"...\"}"
    echo "  reply_comment_in_feed - 回复评论 {\"feed_id\": \"...\", \"xsec_token\": \"...\", \"content\": \"...\", \"comment_id\": \"...\", \"user_id\": \"...\"}"
    echo "  user_profile          - 获取用户主页 {\"user_id\": \"...\", \"xsec_token\": \"...\"}"
    echo "  like_feed             - 点赞 {\"feed_id\": \"...\", \"xsec_token\": \"...\"}  取消: {\"unlike\": true}"
    echo "  favorite_feed         - 收藏 {\"feed_id\": \"...\", \"xsec_token\": \"...\"}  取消: {\"unfavorite\": true}"
    echo "  get_login_qrcode      - 获取登录二维码"
    echo "  delete_cookies        - 删除 cookies，重置登录状态"
    echo "  publish_content       - 发布图文"
    echo "  publish_with_video    - 发布视频"
    exit 1
fi

# 校验 tool name，只允许字母数字和下划线
if [[ ! "$TOOL_NAME" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
    echo "错误: 无效的工具名: $TOOL_NAME"
    exit 1
fi

[ -z "$TOOL_ARGS" ] && TOOL_ARGS="{}"

# 校验 TOOL_ARGS 是合法 JSON
if ! echo "$TOOL_ARGS" | jq empty 2>/dev/null; then
    echo "错误: 参数不是合法的 JSON: $TOOL_ARGS"
    exit 1
fi

# 1. Initialize 并获取 Session ID
INIT_RESPONSE=$(curl --noproxy '*' -s -i -X POST "$MCP_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"openclaw","version":"1.0"}}}')

SESSION_ID=$(echo "$INIT_RESPONSE" | grep -i "Mcp-Session-Id" | awk '{print $2}' | tr -d '\r\n')

if [ -z "$SESSION_ID" ]; then
    echo "错误: 无法获取 MCP Session ID"
    echo "请确保 MCP 服务正在运行: ./start-mcp.sh"
    exit 1
fi

# 2. Initialized notification
curl --noproxy '*' -s -X POST "$MCP_URL" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: $SESSION_ID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null

# 3. Call tool — 使用 jq 安全构建 JSON，避免 shell 注入
CALL_PAYLOAD=$(jq -n \
  --arg name "$TOOL_NAME" \
  --argjson args "$TOOL_ARGS" \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":$name,"arguments":$args}}')

RESULT=$(curl --noproxy '*' -s --max-time 120 -X POST "$MCP_URL" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: $SESSION_ID" \
  -d "$CALL_PAYLOAD")

# 输出结果
echo "$RESULT" | jq .


path: start-mcp.sh
content: #!/bin/bash
# 启动小红书 MCP 服务

XHS_MCP="$HOME/.local/bin/xiaohongshu-mcp"
PID_FILE="$HOME/.xiaohongshu/mcp.pid"
LOG_FILE="$HOME/.xiaohongshu/mcp.log"
XVFB_PID_FILE="$HOME/.xiaohongshu/xvfb.pid"
XVFB_DISPLAY_FILE="$HOME/.xiaohongshu/xvfb.display"

# Cookies 路径（可通过环境变量覆盖）
# XHS_COOKIES_SRC: 源 cookies 文件（用于远程服务器场景）
# 默认检查 ~/cookies.json 和 ~/.xiaohongshu/cookies.json
COOKIES_DST="/tmp/cookies.json"

mkdir -p "$HOME/.xiaohongshu"

# 检测是否有显示器（桌面环境）
has_display() {
    [ -n "$DISPLAY" ] && xdpyinfo >/dev/null 2>&1
}

# 在无桌面环境下自动启动 Xvfb
ensure_display() {
    if has_display; then
        return 0
    fi

    # 已有 Xvfb 在运行
    if [ -f "$XVFB_PID_FILE" ]; then
        local pid
        pid=$(cat "$XVFB_PID_FILE")
        if kill -0 "$pid" 2>/dev/null; then
            export DISPLAY=$(cat "$XVFB_DISPLAY_FILE" 2>/dev/null || echo ":99")
            echo "复用已有 Xvfb (PID: $pid, DISPLAY=$DISPLAY)"
            return 0
        fi
    fi

    # 检查 Xvfb 是否安装
    if ! command -v Xvfb >/dev/null 2>&1; then
        echo "⚠ 未检测到桌面环境，且未安装 Xvfb。"
        echo "  请安装：sudo apt-get install -y xvfb"
        echo "  安装后重新运行本脚本即可自动配置。"
        exit 1
    fi

    echo "未检测到桌面环境，自动启动 Xvfb 虚拟显示..."

    # 自动选择可用的 display 号（99-109）
    local display_num=""
    local d
    for d in $(seq 99 109); do
        if [ ! -e "/tmp/.X${d}-lock" ]; then
            display_num=$d
            break
        fi
        # 锁文件存在但进程已死，尝试清理后使用
        local lock_pid
        lock_pid=$(cat "/tmp/.X${d}-lock" 2>/dev/null | tr -d ' ')
        if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then
            rm -f "/tmp/.X${d}-lock" "/tmp/.X11-unix/X${d}" 2>/dev/null
            if [ ! -e "/tmp/.X${d}-lock" ]; then
                display_num=$d
                break
            fi
        fi
    done

    if [ -z "$display_num" ]; then
        echo "✗ 无法找到可用的 display 号（:99-:109 均被占用）"
        exit 1
    fi

    # -ac: 关闭访问控制，允许 chromium 连接虚拟显示（仅用于 headless 自动化）
    Xvfb ":${display_num}" -screen 0 1024x768x24 -ac >/dev/null 2>&1 &
    echo $! > "$XVFB_PID_FILE"
    echo ":${display_num}" > "$XVFB_DISPLAY_FILE"
    export DISPLAY=":${display_num}"
    sleep 1

    if kill -0 "$(cat "$XVFB_PID_FILE")" 2>/dev/null; then
        echo "✓ Xvfb 已启动 (DISPLAY=:${display_num})"
    else
        echo "✗ Xvfb 启动失败"
        exit 1
    fi
}

# 同步 cookies（支持多个可能的来源）
sync_cookies() {
    local src=""

    # 优先使用环境变量指定的路径
    if [ -n "$XHS_COOKIES_SRC" ] && [ -f "$XHS_COOKIES_SRC" ]; then
        src="$XHS_COOKIES_SRC"
    elif [ -f "$HOME/cookies.json" ]; then
        src="$HOME/cookies.json"
    elif [ -f "$HOME/.xiaohongshu/cookies.json" ]; then
        src="$HOME/.xiaohongshu/cookies.json"
    fi

    if [ -n "$src" ]; then
        if [ ! -f "$COOKIES_DST" ] || [ "$src" -nt "$COOKIES_DST" ]; then
            install -m 600 "$src" "$COOKIES_DST"
            echo "已同步 cookies: $src -> $COOKIES_DST"
        fi
    else
        # 确保已有的 cookies 文件权限正确
        [ -f "$COOKIES_DST" ] && chmod 600 "$COOKIES_DST"
    fi
}

sync_cookies
ensure_display

# 检查是否已在运行
if [ -f "$PID_FILE" ]; then
    PID=$(cat "$PID_FILE")
    if kill -0 "$PID" 2>/dev/null; then
        echo "MCP 服务已在运行 (PID: $PID)"
        echo "如需重启，请先运行 stop-mcp.sh"
        exit 0
    fi
fi

# 解析参数
HEADLESS="true"
PORT="${XHS_MCP_PORT:-18060}"
for arg in "$@"; do
    case $arg in
        --headless=false)
            HEADLESS="false"
            ;;
        --port=*)
            PORT="${arg#*=}"
            ;;
    esac
done

# 校验端口号
if [[ ! "$PORT" =~ ^[0-9]+$ ]]; then
    echo "错误: 无效端口号: $PORT"
    exit 1
fi

# 启动服务
echo "启动小红书 MCP 服务..."
if [ "$HEADLESS" = "false" ]; then
    nohup "$XHS_MCP" -port ":${PORT}" -headless=false > "$LOG_FILE" 2>&1 &
else
    nohup "$XHS_MCP" -port ":${PORT}" > "$LOG_FILE" 2>&1 &
fi

echo $! > "$PID_FILE"
sleep 2

# 验证启动
if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
    echo "✓ MCP 服务已启动 (PID: $(cat $PID_FILE))"
    echo "  端点: http://localhost:${PORT}/mcp"
    echo "  日志: $LOG_FILE"
else
    echo "✗ 启动失败，查看日志: $LOG_FILE"
    cat "$LOG_FILE"
    exit 1
fi


path: stop-mcp.sh
content: #!/bin/bash
# 停止小红书 MCP 服务

PID_FILE="$HOME/.xiaohongshu/mcp.pid"
XVFB_PID_FILE="$HOME/.xiaohongshu/xvfb.pid"

if [ -f "$PID_FILE" ]; then
    PID=$(cat "$PID_FILE")
    if kill -0 "$PID" 2>/dev/null; then
        kill "$PID"
        rm -f "$PID_FILE"
        echo "✓ MCP 服务已停止"
    else
        echo "进程不存在，清理 PID 文件"
        rm -f "$PID_FILE"
    fi
else
    echo "MCP 服务未运行"
fi

# 清理 Xvfb
if [ -f "$XVFB_PID_FILE" ]; then
    XVFB_PID=$(cat "$XVFB_PID_FILE")
    if kill -0 "$XVFB_PID" 2>/dev/null; then
        kill "$XVFB_PID"
        echo "✓ Xvfb 已停止"
    fi
    rm -f "$XVFB_PID_FILE" "$HOME/.xiaohongshu/xvfb.display"
fi


path: xhs_trader_monitor.py
content: #!/usr/bin/env python3
"""
小红书用户帖子关键词监测
每天检查指定用户的最新帖子，标题含关键词时微信推送提醒。

使用前请按 SKILL.md 配置说明填入你自己的信息。
MCP 生命周期由脚本自己管理（启动→监测→关闭）。
"""
import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone, timedelta
from pathlib import Path

# ==========================
# ⚠️ 用户配置区 — 使用前必须修改
# ==========================

# 1. 微信推送目标 ID（openclaw-weixin 通道）
#    获取方式：openclaw message list-accounts --channel openclaw-weixin
TARGET_ID = "[REDACTED_SECRET]"

# 2. 小红书目标用户 ID（从用户主页 URL 或分享链接中获取）
USER_ID = "YOUR_XHS_USER_ID"

# 3. xsec_token（从用户主页任意帖子的 feed 数据中获取 xsecToken）
USER_XSEC = "YOUR_XSEC_TOKEN"

# 4. 监测关键词列表（帖子标题含任一关键词即触发推送）
KEYWORDS = ["棱镜球", "炫彩蛋", "同乘蛋", "祝福项坠"]

# ==========================
# 以下为通用逻辑，通常无需修改
# ==========================

# 脚本目录（skill 的 scripts/ 目录）
SCRIPT_DIR = Path(__file__).parent.resolve()

# 状态文件（记录已通知的帖子 ID，避免重复推送）
STATE_FILE = Path("xhs_trader_state.json")

# 日志文件
MCP_LOG = Path("xhs_trader.log")


def log(msg):
    print(msg)
    try:
        MCP_LOG.parent.mkdir(parents=True, exist_ok=True)
        with open(MCP_LOG, 'a', encoding='utf-8') as f:
            ts = datetime.now(timezone(timedelta(hours=8))).strftime('%Y-%m-%d %H:%M:%S')
            f.write(f"[{ts}] {msg}\n")
    except Exception:
        pass


def start_mcp():
    """启动 MCP 服务"""
    env = os.environ.copy()
    env['PATH'] = '/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
    env['MCP_URL'] = 'http://localhost:18060/mcp'
    
    # 先确保停止旧的
    stop_mcp()
    time.sleep(2)
    
    # 启动
    proc = subprocess.Popen(
        [str(SCRIPT_DIR / "start-mcp.sh"), "--headless=true"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        env=env,
        cwd=str(SCRIPT_DIR)
    )
    
    # 等待服务就绪
    for _ in range(30):
        try:
            result = subprocess.run(
                ["curl", "--noproxy", "*", "-s", "-o", "/dev/null", "-w", "%{http_code}",
                 "-X", "POST", "http://localhost:18060/mcp",
                 "-H", "Content-Type: application/json",
                 "-d", '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"healthcheck","version":"1.0"}}}'],
                capture_output=True, text=True, timeout=5
            )
            if result.stdout.strip() == "200":
                log("  ✅ MCP 服务已就绪")
                return True
        except:
            pass
        time.sleep(1)
    
    log("  ❌ MCP 服务启动超时")
    return False


def stop_mcp():
    """停止 MCP 服务"""
    env = os.environ.copy()
    env['PATH'] = '/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
    subprocess.run(
        [str(SCRIPT_DIR / "stop-mcp.sh")],
        capture_output=True, timeout=30,
        env=env, cwd=str(SCRIPT_DIR)
    )


def load_state():
    if STATE_FILE.exists():
        with open(STATE_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {"notified_ids": [], "last_check": None}


def save_state(state):
    os.makedirs(os.path.dirname(STATE_FILE) if os.path.dirname(STATE_FILE) else ".", exist_ok=True)
    with open(STATE_FILE, 'w', encoding='utf-8') as f:
        json.dump(state, f, ensure_ascii=False, indent=2)


def mcp_call(tool, args_dict):
    try:
        env = os.environ.copy()
        env['PATH'] = '/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
        env['MCP_URL'] = 'http://localhost:18060/mcp'
        
        args_json = json.dumps(args_dict, ensure_ascii=False)
        
        result = subprocess.run(
            [str(SCRIPT_DIR / "mcp-call.sh"), tool, args_json],
            capture_output=True, text=True, timeout=60, env=env,
            cwd=str(SCRIPT_DIR)
        )
        
        if result.returncode != 0:
            log(f"  MCP调用错误: {result.stderr[:200]}")
            return None
        
        return json.loads(result.stdout)
    except Exception as e:
        log(f"  MCP调用失败: {e}")
        return None


def get_user_latest_feeds():
    result = mcp_call("user_profile", {
        "user_id": USER_ID,
        "xsec_token": USER_XSEC
    })
    
    if not result or "result" not in result:
        return []
    
    content = result["result"].get("content", [])
    if not content:
        return []
    
    text_content = content[0].get("text", "")
    try:
        data = json.loads(text_content)
    except:
        return []
    
    return data.get("feeds", [])


def check_keywords_in_feed(feed):
    title = feed.get("noteCard", {}).get("displayTitle", "")
    matched = []
    for kw in KEYWORDS:
        if kw in title:
            matched.append(kw)
    return matched, title


def send_weixin(message):
    try:
        env = os.environ.copy()
        env['HOME'] = str(Path.home())
        env['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
        
        cmd = [
            "openclaw", "message", "send",
            "--channel", "openclaw-weixin",
            "--target", TARGET_ID,
            "--message", message
        ]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)
        return result.returncode == 0 or "✅ Sent" in result.stderr
    except Exception as e:
        log(f"  微信推送失败: {e}")
        return False


def main():
    now = datetime.now(timezone(timedelta(hours=8)))
    log(f"🔍 [{now.strftime('%H:%M')}] 检查目标用户更新...")
    
    # 1. 启动 MCP
    if not start_mcp():
        log("  ❌ MCP 启动失败，本次跳过")
        stop_mcp()
        sys.exit(1)
    
    try:
        # 2. 获取帖子
        state = load_state()
        feeds = get_user_latest_feeds()
        
        if not feeds:
            log("  ❌ 无法获取帖子列表")
            return
        
        # 3. 检查最新帖子
        today_str = now.strftime("%m.%d")
        found = False
        
        for feed in feeds[:5]:
            feed_id = feed.get("id", "")
            title = feed.get("noteCard", {}).get("displayTitle", "")
            
            if not title.startswith(today_str):
                continue
            
            matched, title = check_keywords_in_feed(feed)
            
            if matched:
                if feed_id in state["notified_ids"]:
                    log(f"  ⏭️ 已通知过: {title}")
                    continue
                
                msg = f"🎯 上新提醒！\n\n{title}\n\n检测到关键词：{'、'.join(matched)}\n\n快去查看 👀"
                
                log(f"  🚨 发现关键词！{matched}")
                if send_weixin(msg):
                    log("  ✅ 微信推送成功")
                    state["notified_ids"].append(feed_id)
                    found = True
                else:
                    log("  ❌ 微信推送失败")
        
        state["last_check"] = now.isoformat()
        save_state(state)
        
        if not found:
            log("  ✅ 本轮无目标商品")
    
    finally:
        # 4. 无论如何都关闭 MCP
        stop_mcp()
        log("  🧹 MCP 已关闭")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        log(f"错误: {e}")
        import traceback
        traceback.print_exc()
        # 确保 MCP 关闭
        stop_mcp()
        sys.exit(1)


path: trader_rules.md
content: # 小红书用户帖子关键词监测规则参考

## 前置依赖

1. **xiaohongshu-mcp 二进制**：`~/.local/bin/xiaohongshu-mcp`
2. **xiaohongshu-login 二进制**：`~/.local/bin/xiaohongshu-login`（用于首次登录）
3. **jq**：`apt-get install jq`
4. **Xvfb**（无桌面环境）：`apt-get install xvfb`
5. **openclaw CLI**：用于微信推送
6. **Kimi cookies**：已登录小红书的 cookies 文件

## 获取用户 ID 和 xsec_token

### 方法一：通过小红书 App
1. 打开目标用户主页
2. 点击右上角「分享」→「复制链接」
3. 链接格式：`https://www.xiaohongshu.com/user/profile/USER_ID`
4. 提取 `USER_ID`

### 方法二：通过搜索
1. 用 `search.sh <用户名>` 搜索用户
2. 从返回的 feed 数据中提取 `noteCard.user.userId`

### 获取 xsec_token
1. 从用户主页任意帖子的 feed 数据中提取 `xsecToken`
2. 或运行 `user-profile.sh USER_ID "any"` 后从返回数据中获取有效 token

## 登录方式

本 skill 使用 cookies 登录，不依赖二维码。

**Cookie 文件**：将已登录浏览器的 cookies 导出为 JSON 数组：
```json
[
  {"name": "a1", "value": "...", "domain": ".xiaohongshu.com", "path": "/"},
  {"name": "web_session", "value": "...", "domain": ".xiaohongshu.com", "path": "/"}
]
```

**放置路径**（按优先级）：
1. `~/cookies.json`
2. `~/.xiaohongshu/cookies.json`
3. 环境变量 `XHS_COOKIES_SRC` 指定的路径

**首次登录**（如无 cookies）：
```bash
# 获取二维码
./mcp-call.sh get_login_qrcode
# 用小红书 App 扫码
```

## 监测逻辑

1. 启动 MCP 服务（自动启动 Xvfb 虚拟显示）
2. 调用 `user_profile` 获取用户主页帖子列表
3. 检查最近 5 条帖子标题是否含关键词
4. 只检查**今天**发布的帖子（以 `MM.DD` 日期前缀匹配）
5. 命中关键词且未通知过 → 微信推送
6. 关闭 MCP 服务

## 定时任务建议

```
# 每天 4 次检查
5 8,12,16,20 * * * /usr/bin/python3 /path/to/xiaohongshu-trader-monitor/scripts/xhs_trader_monitor.py >> /path/to/logs/xhs_trader.log 2>&1
```

## 关键词配置

在脚本顶部 `KEYWORDS` 列表中添加/删除关键词：
```python
KEYWORDS = ["棱镜球", "炫彩蛋", "同乘蛋", "祝福项坠"]
```

## 状态文件

- `xhs_trader_state.json` — 记录已通知的帖子 ID，避免重复推送

## 消息模板

```
🎯 上新提醒！

04.30 今日新帖标题...

检测到关键词：棱镜球、炫彩蛋

快去查看 👀
```

## MCP 端口

默认 `http://localhost:18060/mcp`，可通过环境变量 `XHS_MCP_PORT` 修改。

## 故障排查

| 问题 | 解决 |
|------|------|
| MCP 启动超时 | 检查 xiaohongshu-mcp 二进制是否存在、Xvfb 是否安装 |
| 无法获取帖子 | 检查 cookies 是否过期、USER_ID/XSEC 是否正确 |
| 微信推送失败 | 检查 openclaw-weixin 通道是否配置 |
| 二维码登录风控 | 改用 cookies 登录（从已登录浏览器导出） |



Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)