Skill: fuckdayx/clawshop-pro (version 1.3.3)

--- name: clawshop version: "1.3.3" description: Clawshop 自动带货客户端 skill。装上 → 登录账号密码 → /clawshop 直接用,零配置。 metadata: {"openclaw":{"emoji":"🛒"}} command: name: clawshop description: "Clawshop 自动带货(SaaS)" descriptionLocalizations: zh-CN: "Clawshop 自动带货" args: - name: url description: "指定视频URL(抖音/TikTok等),留空则推荐热门" captureRemaining: true --- # Clawshop Skill **Clawshop 自动带货客户端**。装上即用,账号密码登录后所有 AI key、采集系统、视频生成全部由我方服务器统一管理,用户零配置。 > **跟 `clawshop` skill 的区别:** clawshop 是 SaaS 客户端,走 `/api/v1/*` + Bearer token + 短轮询事件队列。 ## ⚠️ 铁律 - **收到触发后，你唯一允许做的事就是调 exec 执行脚本。** 绝对不允许自己编任何文字回复。不说“我先读一下”“让我看看”“收到”“已启动”“请登录”等。所有用户可见的文字100%由脚本内部发出，LLM 永远只调 exec 然后 NO_REPLY。你看到的任何文案模板都是脚本内部用的，不是给你复制粘贴的。 - **发完审核卡片后绝对不发任何额外消息。** 不发“分镜出来了”“等老板点按钮”“已确认生成中”等。 - **每个流程最多 1 次 exec + 1 次 message。** 不允许拆分。 - **遇到异常(超时/失败)唯一允许的动作:告诉老板"XX失败了,原因是YY",然后等指示。** - **不偷偷换方案、不改卡片格式、不改流程。** ## ⛔ 龙虾不准解读脚本 stdout(铁律) **龙虾只负责调脚本 + NO_REPLY。所有给老板看的提示(未登录/登录失败/网络错误/账号密码错误等)一律由脚本内部 `ct.send_text()` 发出。** - 龙虾**不准**根据脚本 stdout 的 `{"ok": false, "reason": "..."}` 自己编话给老板 - 龙虾**不准**把 `reason: not_logged_in` 解读成"接口 404"、"服务挂了"、"请登录"等任何文字 - 龙虾**不准**根据 `recommend_status` / `ack_status` 等数字自己判断状态 - 脚本的 stdout 是给程序/调试看的,不是给老板看的 - 老板要看的所有文字,**必须**由脚本通过 `send_text` 主动推到 Discord **正确流程:** ``` 收到触发 → exec 调脚本 → 脚本内部决定发什么消息 → NO_REPLY ``` **❗ 例外:Discord 斜杠命令触发时,不能 NO_REPLY!** Discord 斜杠命令要求 3 秒内必须有可见回复,否则显示"Command produced no visible reply"。 当触发来源是 `/clawshop` 斜杠命令时: ``` 收到 /clawshop → 发一条短 ack 文本(给 Discord 斜杠命令回复)→ exec 调脚本 → 回复结束(不再补话) ``` 这句 ack 是给 Discord interaction 的回复,不算"额外消息",不违反铁律。 **错误示范(绝对禁止):** ``` 收到触发 → exec 调脚本 → 看 stdout reason → 自己组织语言告诉老板 ``` **为什么这样要求:** 以后会有多个用户用这个 skill,逻辑必须沉淀在代码里。龙虾每次解读 stdout 编话,不同用户/不同会话给出的回答就不一致,且代码无法演进。所有提示语统一由脚本管理才能保持一致行为。 **❗ 例外 2：stdout 以 `OUTPUT_AS_REPLY:` 开头时** 当脚本 stdout 第一行以 `OUTPUT_AS_REPLY:` 开头时，龙虾必须把后面的文本（去掉前缀）**原样**作为回复发出去。这不是“解读”也不是“组织语言”，是机械转发。 - 用于 webchat 等无法走 message tool 主动发送的场景（比如用户还没配 Discord / 飞书）。 - 脚本负责写死文字，龙虾不修改、不添加、不总结、不拆分。 - 转发后不再补任何话。 --- ## 一、整体架构 ``` 用户 OpenClaw 实例 ├── clawshop skill(本 skill) │ ├── data/ │ │ ├── token.json ← 登录后存 │ │ ├── card_map.json ← 短编号 ↔ video_id 映射 │ │ └── processed_events.json ← 已处理事件去重 │ └── lib/ ← 全部脚本 └── 用户配置的 Discord 频道(OpenClaw 自带) → data-service(我方服务器,单一来源) └── /api/v1/* 接口 + 事件队列 ``` --- ## 二、用户使用流程 ### 2.0 安装后命令自动注册 用户 `clawhub install clawshop` 装上 skill 后,需要让 `/clawshop` 在用户的 Discord 服务器里立刻生效。 **为什么不能依赖 OpenClaw 默认同步?** OpenClaw 默认把 skill 命令同步为 Discord **global command**。Global command 在 Discord 端有最长 1 小时缓存,用户装完看不到命令体验差。 **解决方案:首次触发自动注册 guild command(无缓存,立刻生效)** 用户安装完 skill 后,在 Discord 里随便发一句"clawshop"或"带货"(不需要斜杠),LLM 会识别到这个 skill 并触发 `trigger.mjs`。`trigger.mjs` 首次运行时会自动调用 `register-discord.mjs` 把 `/clawshop` 注册为 guild command,注册完用户立刻能在输入框看到 `/clawshop`。 **只执行一次:** `data/registered` 标记文件存在后不再重复注册。如需重新注册(如 bot 加入新 guild),删除 `data/registered` 后再触发一次即可。 **命令:** ``` node ./lib-js/register-discord.mjs ``` **输出示例:** ```json { "ok": true, "app_id": "1496036936996093992", "guild_count": 1, "results": [ { "guild": "用户的服务器", "guild_id": "...", "status": 201, "ok": true, "cmd_id": "..." } ] } ``` **从 OpenClaw 主会话中调用:** 老板可以说"注册 clawshop 命令"或"注册服务器命令",龙虾直接 `exec: node ./lib-js/register-discord.mjs` 跑一次即可。 ### 2.1 首次(脚本内部自己处理,龙虾只负责调脚本) ``` 1. /clawshop → 龙虾 exec trigger.mjs "" 2. trigger.mjs 检测 token.json 不存在 → 自己 send_text("🔑 欢迎使用 Clawshop，请回复账号和密码：`用户名 密码`") 3. 老板回:alice **** 4. 龙虾 exec trigger.mjs "alice ****" 5. trigger.mjs POST /api/v1/auth/login → token 存本地 → 自己 send_text("✅ 登录成功,开始推荐...") 6. trigger.mjs 自动 POST /api/v1/recommend + 启动 cron 轮询 7. cron 拿到 review_1 事件 → 发卡片 ``` ### 2.2 后续 ``` 1. /clawshop → 龙虾 exec trigger.mjs "" 2. trigger.mjs 检测有 token → 直接 POST /api/v1/recommend → 启动轮询 3. cron 拿到事件 → 发卡片 → 老板点按钮 → 调对应 API → 继续轮询 4. 流程结束(review_2 通过/放弃)→ 停止轮询 ``` **注意:** Token 自动保存在 `data/token.json`,登录一次后永不需要重复登录,除非服务端把 token 标记失效(401,脚本会自动清本地 token 并 send_text 提示重新登录)。 --- ## 三、触发方式 响应: - `/clawshop` Discord 斜杠命令(可选 `url:` 参数) - `/推荐` 命令 - 文字消息含 URL(如 `推荐 https://...`) - 系统提示 `Use the "clawshop" skill for this request.` - 含"xxx yyy"格式的消息(两个词=账号密码) - **纯文字 `clawshop`、`带货`、`开始`**(无斜杠,适用于首次安装后还没注册斜杠命令的场景) **首次触发自动注册:** 用户安装 skill 后第一次触发(无论通过斜杠命令还是纯文字),trigger.mjs 会自动调用 `register-discord.mjs` 把 `/clawshop` 注册为 guild command(立即生效,无缓存)。此后用户就能直接用 `/clawshop` 了。只执行一次,标记文件 `data/registered` 存在后不再重复注册。 ### 3.1 URL 提取规则 ```javascript import re def extract_url(msg: str) -> str | None: if not msg: return None s = msg.strip() s = re.sub(r'^\s*(/clawshop|/推荐|推荐|来一个)\s*', '', s, flags=re.I) s = re.sub(r'^\s*url\s*[::]\s*', '', s, flags=re.I) s = s.strip().strip('<>').strip('"\'「」『』').strip() if re.match(r'^https?://', s, flags=re.I): return s return None ``` ### 3.2 登录指令识别 老板首次会发:`用户名 密码`（两个词空格隔开）。 ```javascript import re m = re.match(r'^\s*(登录|login)\s+(\S+)\s+(\S+)\s*$', msg) if m: username, [REDACTED_SECRET]), m.group(3) ``` ### 3.3 触发统一脚本 **所有 exec 命令必须带 `workdir` 参数,指向本 skill 目录(即 SKILL.md 所在目录)。** **必须传入渠道参数!** 第二个参数是消息来源渠道(discord/feishu/telegram/slack等),第三个参数是 target(如飞书的 `user:ou_xxx` 或 discord 的 `channel:xxx`),确保回复发往正确的渠道和对象。 ``` exec: node ./lib-js/trigger.mjs "<老板原始消息文本>" "<channel>" "<target>" workdir: <本 skill 目录> ``` 实际调用时,模型应该用 exec 工具的 workdir 参数: ```json {"command": "node ./lib-js/trigger.mjs \"<消息>\" \"<channel>\" \"<target>\"", "workdir": "<skill目录绝对路径>"} ``` **channel 值从当前会话的 inbound metadata 获取(如 `"channel": "feishu"` 或 `"channel": "discord"`)。** **target 值从当前会话的 deliveryContext 获取(如 `"to": "user:ou_xxx"`)。如果不确定 target,传空字符串让脚本自动检测。** Skill 目录路径可通过 `SKILL.md` 所在位置确定,不同系统不同: - Windows: `C:\Users\xxx\openclaw\skills\clawshop-pro` - macOS: [REDACTED_SECRET] - Linux: [REDACTED_SECRET] ``` 脚本内部: 1. **解析消息** - 是 `xxx yyy`（两个词）→ 走登录流程 - 否 → 走推荐流程 2. **登录流程** - POST `/api/v1/auth/login` `{username, password}` - 成功 → 存 token 到 `data/token.json` → 发"✅ 登录成功,开始推荐"消息 → 继续走推荐流程 - 失败 → 发"❌ 账号密码错误"消息 → 退出 3. **推荐流程** - 检查本地 `token.json`:不存在 → 发"请回复账号和密码：`用户名 密码`" → 退出 - 提取 URL(同 3.1 规则) - 立即发 ack 文本到 Discord(解决斜杠命令 3s 超时) - 无 URL:`⏳ 已启动热门推荐,捐到合适的视频再给您发审核卡片。` - 有 URL:`⏳ 指定视频已接收,正在抓取分析,完成后给您发审核卡片。\n<url>` - POST `/api/v1/recommend` `{}` 或 `{"video_url": "..."}` - **启动 cron 轮询任务**(见第六节) - 脚本退出,本轮 NO_REPLY **绝对禁止:** 直接 `requests.post(...)` 绕开脚本;拆成多次 tool call。 --- ## 四、API 客户端 所有 `/api/v1/*` 接口共用同一个客户端(`lib-js/api.mjs`),自动加 `Authorization: Bearer <token>` header。 ### 4.1 业务接口(9 个) | 函数 | 路径 | 说明 | |---|---|---| | `login(u, p)` | `POST /api/v1/auth/login` | 拿 token | | `recommend(url=None)` | `POST /api/v1/recommend` | 启动推荐 | | `task_approve(vid, step)` | `POST /api/v1/tasks/{vid}/approve` 或 `/{step}/approve` | 通过 | | `task_reject(vid, step)` | `POST /api/v1/tasks/{vid}/reject` | 跳过/放弃 | | `task_change_product(vid)` | `POST /api/v1/tasks/{vid}/change-product` | 换商品 | | `task_storyboard_redo(vid)` | `POST /api/v1/tasks/{vid}/storyboard/redo` | 重做分镜 | | `task_video_redo(vid)` | `POST /api/v1/tasks/{vid}/video/redo` | 重做视频 | ### 4.2 事件接口(2 个) | 函数 | 路径 | 说明 | |---|---|---| | `events_pending()` | `GET /api/v1/events/pending` | 拉积压事件 | | `event_ack(eid)` | `POST /api/v1/events/{eid}/ack` | 确认消费 | ### 4.3 错误处理 - `401 invalid_token` → 删本地 token.json,发"❌ 身份已过期,请重新发送账号和密码：`用户名 密码`" - `403 forbidden` → 发"❌ 操作越权" + 错误内容 - `5xx` → 发"❌ 服务器错误,请稍后重试" - 超时 → 发"❌ 请求超时" --- ## 五、事件处理(核心) ### 5.1 事件类型 事件格式: | event_type | 触发 | data 字段 | 用户可见提示(脚本内部发) | |---|---|---|---| | `review_1` | 一审就绪 | video_id, video_url, author, view_count, score, summary, product_name, product_url, product_image_url | 发一审卡片(`send-review-1.mjs`) | | `review_1_5` | 分镜就绪 | video_id, storyboard_url(或 error)| 有 storyboard_url → 发分镜卡片;有 error → `⚠️ 视频 #{n} 分镜生成失败:{error}` | | `review_2` | 视频就绪 | video_id, video_url(或 error)| 有 video_url → 发视频卡片;有 error → `⚠️ 视频 #{n} 生成失败:{error}` | | `published` | 发布完成 | video_id, platform, status | `✅ 视频 #{n} 已成功发布到 {platform}!` | | `no_match` | 没找到 | (空)| `🙅 这轮没找到合适的视频,稍后再试。` | | `error` | 异常 | error_type, message | `⚠️ 服务端异常:{message}` | ### 5.1.1 错误事件特殊处理 - `error_type: "quota_exhausted"` → `⚠️ API余额不足,无法继续生成。请充值后重试。\n错误详情:{error内容}` - 其他 error_type → `⚠️ 服务端异常:{message}` **所有提示文案由脚本内部 `ct.send_text()` 发出,龙虾不准自己编。** ### 5.2 事件处理脚本 ``` exec: node ./lib-js/poll-events.mjs ``` **轮询脚本职责(一次 exec 干完):** 1. 调 `events_pending()` 拉所有未处理事件 2. 对每个事件: - 检查 `processed_events.json`,处理过 → 直接 ack 跳过 - 没处理过 → 按 event_type 分发到对应 send-review-*.mjs / 处理函数 - 处理成功 → 写入 processed_events + 调 ack 3. 检查是否流程结束(review_2 通过、所有 reject、no_match)→ 停止 cron 4. 输出 JSON 摘要:`{"processed": N, "stopped": true/false}` ### 5.3 子脚本 - `lib-js/send-review-1.mjs` - 发一审卡片 - `lib-js/send-review-1-5.mjs` - 发分镜卡片 - `lib-js/send-review-2.mjs` - 发视频卡片 - `lib-js/handle-button.mjs` - 按钮回调 - `lib-js/card-tools.mjs` - 卡片工具(卡片工具) --- ## 六、Cron 轮询任务 ### 6.1 启动 `lib-js/trigger.mjs` 在推荐启动后**注册一个 cron job**: ```javascript # 通过 OpenClaw gateway HTTP API 注册 POST http://127.0.0.1:18789/tools/invoke Body: { "tool": "cron", "args": { "action": "add", "job": { "name": "clawshop-poll", "schedule": {"kind": "every", "everyMs": 5000}, "payload": { "kind": "agentTurn", "message": "Clawshop 轮询事件: 执行 `node ./lib-js/poll-events.mjs`,有事件就处理;处理完后回复 NO_REPLY。", "lightContext": true }, "deleteAfterRun": false, "delivery": {"mode": "none"} } } } ``` **关键参数:** - `everyMs: 5000` - 每 5 秒 - `lightContext: true` - 不带历史上下文,省 token - `delivery.mode: "none"` - 不发系统消息,由脚本自己发卡片 - `name: clawshop-poll` - 固定 ID 方便后续 update/remove ### 6.2 自动停止 **触发停止的条件:** 1. 流程结束事件(`review_2 approve` / `published` / `reject` 全部 / `no_match`) 2. 启动超过 15 分钟(max_duration_seconds) `poll-events.mjs` 内部检查后调: ```javascript POST http://127.0.0.1:18789/tools/invoke Body: { "tool": "cron", "args": {"action": "remove", "id": "clawshop-poll"} } ``` ### 6.3 启动时间记录 `data/poll_state.json`: ```json {"started_at": 1716180000, "active": true} ``` 每次 `poll-events.mjs` 启动时检查 `now - started_at > 900` → 强制停止 cron。 --- ## 七、按钮回调 收到 `Clicked "✅ 做 #4"` 等按钮消息: ``` exec: node ./lib-js/handle-button.mjs "<clicked_label>" "<channel>" ``` **脚本内部:** 1. 正则提取 `#<short_id>` → 查 card_map → 拿 video_id 和 step 2. 按按钮前缀映射到 action,**先发进度消息(不许改)** 3. 调对应的 API(不再是 `/clawshop/callback`,而是 `/api/v1/tasks/.../xxx`): | 按钮 | 阶段 | action | 进度消息(老板可见) | API 调用 | |---|---|---|---|---| | ✅ 做 | review_1 | approve | `⏳ 视频 #{n} 开始生成分镜图,请稍候...` | `task_approve(vid, "review_1")` → `POST /api/v1/tasks/{vid}/approve` | | ⏭️ 换一个 | review_1 | reject + recommend | 无(不发进度) | `task_reject(vid)` + `recommend()` | | 🔄 换商品 | review_1 | change_product | `🔄 正在重新匹配商品...` | `task_change_product(vid)` | | ✅ 分镜通过 | review_1_5 | approve | `⏳ 分镜已确认,正在生成视频提示词和视频,预计8-10分钟...` | `task_approve(vid, "review_1_5")` → `POST /api/v1/tasks/{vid}/storyboard/approve` | | 🔄 重新生成 | review_1_5 | redo | `🔄 正在重新生成分镜图...` | `task_storyboard_redo(vid)` | | ❌ 放弃 | review_1_5/review_2 | reject | `❌ 已放弃视频 #{n}` | `task_reject(vid)` + 删 card_map + 停 cron | | ✅ 通过 | review_2 | approve | `📤 视频 #{n} 正在提交发布...` | `task_approve(vid, "review_2")` → `POST /api/v1/tasks/{vid}/video/approve` | | 🔄 重做 | review_2 | redo | `🔄 视频 #{n} 重新生成中...` | `task_video_redo(vid)` | 4. 按钮 review_2 approve / reject 全部 → 删 card_map + 停 cron 5. 按钮回调后**继续 cron 轮询**(等下一个事件) --- ## 八、本地状态文件 | 文件 | 用途 | 示例 | |---|---|---| | `data/token.json` | 登录 token | `{"token": "abc...", "username": "alice"}` | | `data/card_map.json` | 短编号 ↔ video_id | `{"date": "2026-05-20", "next_id": 3, "cards": {...}}` | | `data/processed_events.json` | 已 ack 的 event_id 集合 | `{"ids": [1001, 1002, 1003]}` | | `data/poll_state.json` | 轮询启动时间 | `{"started_at": 1716180000, "active": true}` | --- ## 九、消息处理规则 | 收到的消息 | 动作 | |---|---| | `/clawshop` / `/推荐` / `推荐 URL` / 裸 URL | 触发 `lib-js/trigger.mjs` | | `xxx yyy`（账号 密码）| 触发 `lib-js/trigger.mjs`(脚本内部识别)| | `Clicked "..."` 按钮回调 | 触发 `lib-js/handle-button.mjs` | ### 9.1 按钮回调必须先回复再执行(铁律) Discord 按钮点击后只给 **3 秒**响应窗口。`handle-button.mjs` 要调 data-service API,经常超过 3 秒。 **正确流程(所有按钮/命令都遵守):** ``` 收到 Clicked "..." 或 /clawshop 或登录消息 → 立刻回复 "✅ 收到"(占住 3s 窗口) → 然后 exec 对应脚本 → NO_REPLY ``` **铁律:LLM 回复永远只写 `✅ 收到`,绝对不允许写具体进度/提示文案,避免与脚本重复发送。** **示例:** ``` 用户: Clicked "✅ 做 #3" 助手: ⏳ 视频 #3 开始生成分镜图,请稍候... [exec: node ./lib-js/handle-button.mjs "✅ 做 #3"] NO_REPLY ``` ``` 用户: Clicked "⏭️ 换一个 #5" 助手: ✅ 收到 [exec: node ./lib-js/handle-button.mjs "⏭️ 换一个 #5"] NO_REPLY ``` **绝对禁止:** 先调脚本等结果再回复。这会导致 Discord 显示"该交互失败"。 | Cron `Clawshop 轮询事件: ...` | 触发 `lib-js/poll-events.mjs` | | 系统提示 `Use the "clawshop" skill ...` | 按上下文判断走哪个脚本 | | HEARTBEAT_OK / inter-session announce | NO_REPLY | --- ## 十、隔离规则 - **绝不调 `/recommend` `/clawshop/callback`,只调 `/api/v1/*`** clawshop 完全独立运行。 --- ## 十一、依赖 - data-service(地址在 `config.json` 的 `data_service.base_url`) - OpenClaw gateway(`http://127.0.0.1:18789`) - OpenClaw cron 工具 - OpenClaw message 工具(发 Discord 卡片) - 用户配置的 Discord(自带,不依赖特定频道 ID - 由 OpenClaw 路由) --- ## 十二、注意事项(血泊教训) 1. **token 失效后必须删本地 token.json 再提示老板**,不能光提示不删。 2. **processed_events.json 防重复发卡片**,必须先写文件再 ack(保证 ack 失败也不会重发)。 3. **cron job 启动后必须能停**:流程结束、超时、报错都要停,避免无限轮询烧 token。 4. **lightContext: true** 必须设,否则每次轮询都加载全部上下文,token 暴涨。 5. **登录消息不要 echo 密码**,"已收到登录请求"就够了。 6. **审核卡片走老板自己的 Discord**(OpenClaw message 工具 channel: discord,不指定 channel_id,让 OpenClaw 自动路由到用户配置的频道)。 7. **inter-session 来的 HEARTBEAT_OK / Agent-to-agent announce 消息,一律回 `NO_REPLY`。** 回任何其他文字都会被发到 Discord 频道里污染老板的屏幕。 8. **收到事件但数据不完整(如 storyboard_url 为空),且没有 error 字段,静默跳过。** 不要跟老板说"还没生成好"之类的废话。 9. **任何 review_1 / review_1_5 / review_2 事件进来 → 必须立即调对应 send-review-*.mjs 发卡片。绝对不允许做"是不是重复"的判断,不允许跳过。** 即使同一个 video_id 反复出现(比如换商品后重推 review_1),也是新卡片,必须发。 10. **按钮回调必须查映射文件确认 video_id,不能猜。** 查不到就问老板,不要自己猜。 ---


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