在整个对话中维护以下变量,进入任意能力时先检查所需变量是否已知,缺少时用表中"询问用户"的方式补全:
| 变量 | 含义 | 获取方式 | 如何向用户询问 |
|---|---|---|---|
| ------ | ------ | --------- | -------------- |
| cityId | 城市ID | 询问城市名 → get-cities.mjs 匹配返回的城市列表取 id | "您在哪个城市?" |
| movieId | 影片ID | search-movies / get-hot-movies | "您想看哪部电影?" |
| cinemaId | 影院ID | search-cinemas / get-nearby-cinemas | "您想去哪家影院?" |
| seqNo | 场次号 | get-showtimes.mjs | "您想看哪个场次?(日期+时间)" |
| ticketCount | 购票张数 | 用户确认 | "您要买几张票?" |
| authKey | 认证密钥 | load-authkey.mjs | 不询问用户,走【原子动作:登录】 |
| selectedSeats | 用户选中座位的标识列表,仅含 rowId + columnId,不含完整座位数据;下单时必须用 rowId + columnId 去 seatMapRows 查完整对象 | 用户确认后记录 | 不询问用户,展示座位图后由用户确认 |
| orderId | 订单号 | create-order.mjs | 不询问用户,下单后自动获取 |
| seatMapRows | 完整座位行数据 | get-seat-map 返回的 regions[0].rows | 不询问用户,调完 get-seat-map 后自动缓存,换座位时传给 render-seat-map.mjs |
| channel | 当前会话渠道 | 直接从当前对话上下文读取,禁止自行生成、猜测或修改。示例:openclaw-weixin(微信)、feishu(飞书)。不同平台值不同,以实际上下文为准。无法获取时传空字符串,脚本自动降级 | 不询问用户 |
| targetId | 当前用户ID | 直接从当前对话上下文读取,禁止自行生成、猜测或修改。示例:o9cq8xxxxx@im.wechat。不同平台格式不同,以实际上下文为准。无法获取时传空字符串,脚本自动降级 | 不询问用户 |
> 脚本详细入参/出参格式见 references/scripts-api.md(需要时主动读取)
echo '<JSON>' | node "{baseDir}/scripts/<script>.mjs"
每个能力可独立触发。触发时先检查前置变量,缺什么补什么,再执行。
触发:「买电影票」「我要买票」「帮我买票」「买票」「购票」「我想看电影」「推荐电影」「最近有什么电影」「帮我选部电影」「有什么好看的」「想看电影」
> 用户意图模糊(未说具体影片)时,默认进入此能力,先展示热映再让用户选,不要先问城市
前置检查:无
执行:
echo '{"ci": <cityId 或 1>}' | node scripts/get-hot-movies.mjs
输出格式(必须使用表格,不得改变结构):
| 影片名称 | 评分 | 主演 | 时长 | 类型 |
|---|---|---|---|---|
| --------- | ------ | ------ | ------ | ------ |
| 《xxx》 | x.x分 | xxx | xxx分钟 | xxx |
结尾固定:「想看哪部?告诉我,给您查场次!」
完成后:等待用户选片 → 更新 movieId → 进入【能力 3:查影院】
触发:用户说出具体影片名,如「帮我买飞驰人生3」
前置检查:
get-cities.mjs 查询对应 cityIdecho '{}' | node scripts/get-cities.mjs
# 从返回列表中匹配用户说的城市名,取对应 id 作为 cityId
执行:
echo '{"keyword": "<影片名>", "cityId": <cityId>}' | node scripts/search-movies.mjs
输出格式:同能力 1
完成后:更新 movieId → 进入【能力 3:查影院】
触发:用户选定影片后,或直接说「附近有什么影院」「查万达影城」
前置检查:
get-cities.mjs 查询对应 cityIdecho '{}' | node scripts/get-cities.mjs
# 从返回列表中匹配用户说的城市名,取对应 id 作为 cityId
执行(三选一,详细入参见 references/scripts-api.md):
get-cinemas-by-movie.mjsget-nearby-cinemas.mjssearch-cinemas.mjs输出格式(必须使用表格):
| 影院名称 | 地址 | 距离 | 最低票价 |
|---|---|---|---|
| --------- | ------ | ------ | --------- |
| xxx | xxx路xxx号 | x.xkm | ¥xx起 |
完成后:用户选定影院 → 更新 cinemaId → 进入【能力 4:查场次】
触发:用户选定影院后、直接说「查一下场次」,或用户提到影院名但未指定影片时
前置检查:
执行:
echo '{"cinemaId": <cinemaId>, "cityId": <cityId 或 1>}' | node scripts/get-showtimes.mjs
处理返回结果:
movies.find(m => m.name.includes("影片名")) 筛选,只展示该影片的场次输出格式(按时间段分类):
《xxx》- x月x日场次
上午(12点前):
- xx:xx 国语 2D ¥xx
- xx:xx 英语 IMAX ¥xx
下午(12-18点):
- xx:xx 国语 2D ¥xx ← 推荐
晚上(18点后):
- xx:xx 国语 3D ¥xx
> 已过的场次不展示;推荐视野好、价格适中的场次
完成后:用户选定场次 → 更新 seqNo → 询问「您要买几张票?」→ 更新 ticketCount → 进入【原子动作:登录】
触发:「看一下座位图」「帮我选个座位」「选座」,或完成登录后自动进入
前置检查:
分支 A:首次展示推荐座位
echo '{"seqNo": "<seqNo>", "ticketCount": <ticketCount>}' | node scripts/get-seat-map.mjs
缓存返回值的 regions[0].rows 到 seatMapRows。
输出格式(必须按此顺序,不得省略):
seatMapText(原样输出,不改动)priceInfo分支 B:用户要换座位
触发:用户在推荐后说换座位,或下单失败(1004)后重新选座
> 此分支依赖已缓存的 seatMapRows,无需重新调用 get-seat-map.mjs
询问:「请告诉我您想要的排数和座位号(如:5排8座)」
用户说出后,从 seatMapRows 中找到对应座位的 rowId 和 columnId,调用 render-seat-map.mjs 重新渲染(详细入参见 references/scripts-api.md):
echo '{"rows": <seatMapRows>, "centerSeats": [{"rowId": "<rowId>", "columnId": "<columnId>", "mark": "★"}]}' | node scripts/render-seat-map.mjs
{ seatNotFound: true } → 告知「这个座位不存在,请重新告诉我排数和座位号」→ 重复分支 BseatMapText,询问:「确认选这个位置吗?」触发:用户确认座位后自动进入
前置检查:需要 seqNo、selectedSeats、ticketCount、authKey
展示内容(逐项列出):
| 项目 | 内容 |
|---|---|
| ------ | ------ |
| 影片 | 《xxx》 |
| 影院 | xxx影城 |
| 场次 | x月x日 xx:xx 国语2D |
| 座位 | 第X排Y座、Z座 |
| 张数 | x张 |
| 总价 | ¥xxx |
完成后:进入【原子动作:下单支付】
触发:「出票了吗」「查一下出票状态」「支付完了」,或用户告知已支付后自动触发
前置检查:
执行:
echo '{"orderId": "<orderId>"}' | node scripts/query-ticket-status.mjs
状态判断:
payStatus === 1 && uniqueStatus === 9 → 出票成功,展示取票码和二维码payStatus === 1 && uniqueStatus < 9 → 出票中,告知「正在出票,请稍等1-2分钟后再查」payStatus === 0 → 未支付,引导支付uniqueStatus === 10 → 出票失败,参考 error-codes.md 处理> 以下两个动作必须完整执行所有步骤,任何步骤失败即整个动作失败。
> 执行期间禁止在步骤之间输出任何分析、过渡或解释文字,唯一允许对用户输出的内容是 Step 3 的固定文案。
何时执行:进入选座/下单前,或检测到 authKey 无效时
Step 1 — 检查本地 AuthKey
echo '{}' | node scripts/load-authkey.mjs
{ "token": "xxx", "nickname": "xxx" } → 展示「当前账号:猫眼用户-{昵称},继续使用还是更换账号?」{ "token": null } → 执行 Step 2Step 2 — 获取登录链接(必须得到一个有效链接才能进入 Step 3)
优先调用 send-qr.mjs 生成发送命令( 和 必须原样取自会话上下文,禁止修改):
echo '["auth", {"context": {"channel": "<channel>", "targetId": "<targetId>"}}]' | node scripts/send-qr.mjs
{ "success": true, "execCommand": "..." } → 使用 exec 工具执行该命令,将二维码和链接发送给用户,Step 3 文案无需附加链接{ "fallback": true, "fallbackLink": "" } → 取 fallbackLink 作为登录链接,Step 3 文案末尾附加若 send-qr.mjs 未被调用、调用失败或未返回有效链接,必须兜底:
echo '{}' | node scripts/get-authkey-link.mjs
# 取返回值 data.authKeyUrl 作为登录链接
Step 3 — 发送固定文案(不得修改内容,必须包含登录链接)
需要先完成一步登录,扫码或点击链接获取认证密钥,粘贴回来后我会继续帮您自动完成后续所有流程,不再打扰你 👍
💡 如果扫码后无法获取,可点击右上角选择用默认浏览器打开~
文案末尾必须附加登录链接(来自 Step 2 的 fallbackLink 或兜底的 authKeyUrl):
🔗 [点击获取认证密钥](<登录链接>)
> 即使用户之前说"不需要问我",此步必须等用户操作,发完文案后静默等待,不要追问其他问题
Step 4 — 等待用户粘贴 AuthKey,验证并保存
echo '{"token": "<用户粘贴的值>"}' | node scripts/validate-maoyan-authkey.mjs
# 验证成功后,取返回值中的 token/userId/userName 一并保存
echo '{"token": "<token>", "userId": "<userId>", "userName": "<userName>"}' | node scripts/save-authkey.mjs
何时执行:用户在能力 6 确认订单后
Step 1 — 创建订单(详细入参格式见 references/scripts-api.md)
echo '{"seqNo": "<seqNo>", "cityId": <cityId>, "seats": {"count": <ticketCount>, "list": [<座位对象列表>]}}' | node scripts/create-order.mjs
> ⚠️ seats.list 构建方式:遍历 selectedSeats(仅含 rowId + columnId),每项去 seatMapRows 查完整座位对象,取以下全部字段填入。禁止直接使用 recommendedSeats 或 selectedSeats 本身作为 seats.list 参数(字段不完整)。
> ```
> 对 selectedSeats 中每项(rowId, columnId):
> row = seatMapRows.find(r => r.rowId === rowId)
> seat = row.seats.find(s => s.columnId === columnId)
> 必填字段(缺一不可):
> rowId ← seat.rowId
> columnId ← seat.columnId
> seatNo ← seat.seatNo(不可自行构造)
> seatStatus ← seat.seatStatus
> seatType ← seat.seatType
> type ← seat.seatType(与 seatType 相同)
> sectionId ← seat.sectionId
> sectionName ← seat.sectionName
> ```
{ "orderId": "xxx" } → 更新 orderId → 执行 Step 2Step 2 — 获取支付链接(必须得到一个有效链接才能进入 Step 3)
优先调用 send-qr.mjs 生成发送命令( 和 必须原样取自会话上下文,禁止修改):
echo '["pay", {"context": {"channel": "<channel>", "targetId": "<targetId>"}}]' | node scripts/send-qr.mjs
{ "success": true, "execCommand": "..." } → 使用 exec 工具执行该命令,将二维码和链接发送给用户,Step 3 文案无需附加链接{ "fallback": true, "fallbackLink": "" } → 取 fallbackLink 作为支付链接,Step 3 文案末尾附加若 send-qr.mjs 未被调用、调用失败或未返回有效链接,必须兜底:
echo '{}' | node scripts/get-payment-link.mjs
# 取返回值 data.paymentUrl 作为支付链接
Step 3 — 发送固定文案(不得修改内容,必须包含支付链接)
✅ 订单创建成功!座位已为您锁定约15分钟。
请扫描二维码或点击链接前往支付。
💡 如果扫码后无法支付,可点击右上角选择用默认浏览器打开~
⚠️ 重要提醒:
- 请在15分钟内完成支付,否则座位将被释放
- 支付完成后,请返回对话告诉我,我会为您查询出票状态
- 支付成功 ≠ 出票成功,需要等待出票完成才能取票
文案末尾必须附加支付链接(来自 Step 2 的 fallbackLink 或兜底的 paymentUrl):
🔗 [点击前往支付](<支付链接>)
语气示例(参考,不要照抄):
共 1 个版本