← 返回
未分类 Key

travel-city-game(旅行游戏副本生成器)

根据用户输入的城市名,自动调用 flyai 搜索该城市的景点、体验、美食产品, 然后生成一个游戏化的3节点叙事行程 H5 页面。每个节点包含 AI 生成的故事文案 和飞猪真实预订链接。使用方式: /travel-city-game 城市名。 当用户提到 "travel-city-game"、"生成行程"、"旅行叙...
根据用户输入的城市名,自动调用flyai搜索该城市的景点、体验、美食,生成游戏化的三节点叙事行程H5页面。每个节点包含AI生成的故事文案和飞猪真实预订链接。使用方式:/travel-city-game 城市名。当用户提到“travel-city-game”“生成行程”“旅行叙”等关键词时触发。
hongweisong1999 hongweisong1999 来源
未分类 clawhub v1.0.9 1 版本 100000 Key: 需要
★ 3
Stars
📥 689
下载
💾 0
安装
1
版本
#latest

概述

City Game — AI 叙事行程生成器

将任意城市的真实旅游产品串联成一条游戏化叙事路线。玩家逐步解锁节点、预订真实飞猪产品、收集奖励,体验"剧情驱动"的旅行方式。

输入

用户提供一个城市名,如 长沙大理厦门 等。从用户消息中提取城市名,赋值给变量 CITY

完整工作流程

Step 0: 验证 flyai CLI 已就绪

本 Skill 依赖 flyai Skill 提供的 CLI 工具。flyai 必须由用户预先安装(不在运行时自动安装,以避免供应链安全风险)。

执行以下命令确认 flyai 可用:

flyai --help

如果命令不存在(返回 "command not found"),停止执行并提示用户

> ⚠️ 未检测到 flyai CLI。请先安装 flyai Skill:

> - 方式一:访问 https://clawhub.ai/yealexchen/flyai 安装

> - 方式二:在终端执行 claude skill install flyai

>

> 安装完成后重新运行本 Skill。

绝对不能在 Skill 流程中自动执行 npm install 安装 flyai——这会引入未经用户确认的第三方代码,存在供应链安全风险。同样,绝对不能跳过 flyai 搜索直接编造数据。

API 限流处理

在后续 Step 1 的搜索过程中,如果 flyai 返回了限流相关的错误(如 HTTP 429、"rate limit"、"quota exceeded" 或返回结果为空且提示频率过高),停止继续请求并提示用户获取专属 API Key

> ⚠️ flyai API 请求被限流,免费额度可能已用尽。请按以下步骤获取 API Key:

> 1. 登录 https://flyai.open.fliggy.com/

> 2. 注册/登录后在控制台获取你的专属 API Key

> 3. 将 API Key 配置到环境变量:export FLYAI_API_KEY="你的Key"

> 4. 配置完成后重新运行本 Skill

设置好 API Key 后,flyai CLI 会自动使用该 Key 进行认证,不再受公共限流约束。

Step 1: 用 flyai CLI 搜索 3 类旅游产品

安全执行 flyai 命令(避免 Shell 注入)

⚠️ 严禁直接将 CITY 变量拼接到 Bash 命令字符串中执行。必须使用以下安全方式:

方式一:使用 Python 子进程调用(推荐,最安全)

用 Python 执行 flyai 命令,参数作为列表传递(不经过 Shell 解析):

import subprocess, json

def flyai_search(args):
    """安全调用 flyai CLI,参数作为列表传入,Shell 无法解析特殊字符"""
    result = subprocess.run(
        ["flyai"] + args,
        capture_output=True, text=True, timeout=30
    )
    if result.returncode != 0:
        print(f"Error: {result.stderr}")
        return None
    return json.loads(result.stdout)

# 示例:搜索历史古迹
data = flyai_search(["search-poi", "--city-name", CITY, "--category", "历史古迹"])

方式二:如果必须用 Bash,使用单引号包裹所有变量

# 使用单引号包裹 CITY 和其他参数值,防止 Shell 解析
flyai search-poi --city-name 'CITY' --category '历史古迹'

> ⚠️ 方式二仍有风险(如果 CITY 本身包含单引号),优先使用方式一。

绝对禁止的做法

# ❌ 危险!CITY 如果包含 " 或 ; 等字符会导致命令注入
flyai search-poi --city-name "CITY" --category "历史古迹"

使用上述安全方式执行以下 flyai 搜索。每条命令返回 JSON 到 stdout。

重要:两种命令的返回结构不同:

  • search-poi 返回: data.itemList[].{name, address, mainPic, jumpUrl, ticketInfo.ticketName}
  • keyword-search 返回: data.itemList[].info.{title, picUrl, jumpUrl, price, tags}

搜索 A — 节点1(文化探索):历史/文化景点

首选命令(使用单引号):

flyai search-poi --city-name 'CITY' --category '历史古迹'

如果返回结果少于 2 个,依次尝试:

flyai search-poi --city-name 'CITY' --category '人文古迹'
flyai search-poi --city-name 'CITY' --category '博物馆'
flyai keyword-search --query 'CITY 著名景点门票'

从结果中选 1 个最佳产品,提取字段:

  • skuName: 来自 name(search-poi)或 info.title(keyword-search)
  • jumpUrl: 来自 jumpUrl(search-poi)或 info.jumpUrl(keyword-search)
  • picUrl: 来自 mainPic(search-poi)或 info.picUrl(keyword-search)
  • address: 来自 address(search-poi)或自行根据产品名推断(keyword-search 无 address 字段)

搜索 B — 节点2(特色体验):演出/体验/特色项目

首选命令(使用单引号):

flyai keyword-search --query 'CITY 特色体验 演出'

如果返回结果不理想,依次尝试:

flyai keyword-search --query 'CITY 非遗体验'
flyai search-poi --city-name 'CITY' --category '演出赛事'
flyai keyword-search --query 'CITY 休闲玩乐'

从结果中选 2 个产品(主产品 + 备选产品,备选用于 Step 4 意外引擎)。


搜索 C — 节点3(美食收官):美食/夜游/特色民宿

首选命令(使用单引号):

flyai keyword-search --query 'CITY 美食体验'

如果返回结果不理想,依次尝试:

flyai keyword-search --query 'CITY 夜游'
flyai keyword-search --query 'CITY 特色民宿'
flyai keyword-search --query 'CITY 网红餐厅'

从结果中选 1 个最佳产品


Step 2: 验证所选产品

确认每个所选产品都满足以下条件(必须全部满足):

  • jumpUrl 存在且为有效 URL(非空字符串)
  • picUrl 存在且为有效图片 URL
  • skuName 非空且有地域特色

如果某个产品不满足,从同一搜索结果中选择下一个产品,或换关键词重新搜索。

Step 3: 为每个节点生成叙事故事

为 3 个节点分别撰写沉浸式第二人称故事文案(80-120 字),格式要求:

  • 使用第二人称"你"作为主角
  • 包含场景描写和感官细节(视觉、听觉、味觉、触觉至少 2 种)
  • 包含一个 NPC 角色的对话(如守门大爷、老板娘、茶馆掌柜、夜市摊主)
  • 以省略号或悬念结尾
  • 融入该城市独有的文化元素和地标特征(不要写任何城市都通用的万能文案)

三章结构:

  1. 第一章 — 到达城市,被某个历史/文化元素吸引,节奏舒缓
  2. 第二章 — 发现城市的隐藏魅力,节奏加快,情节推进
  3. 终章 — 夜幕降临,在美食或夜景中完成旅程,余韵悠长

Step 4: 构建意外引擎数据

为节点2设置意外引擎,模拟"产品售罄→AI 重新搜索→剧情反转"的体验。使用 Step 1 搜索 B 的备选产品作为替代:

{
  "reason": "很抱歉,{主产品名} 的热门场次已售罄,节假日期间一票难求...",
  "aiThinking": "FlyAI 正在重新搜索 CITY 周边体验项目...\n\n发现了一个隐藏宝藏:{备选产品名}!",
  "newStory": "为备选产品写的新故事文案(80-120字)",
  "newSkuName": "备选产品的 skuName",
  "newJumpUrl": "备选产品的 jumpUrl",
  "newPicUrl": "备选产品的 picUrl",
  "newAddress": "备选产品的 address",
  "compensationReward": { "type": "coupon", "name": "剧情反转补偿券", "icon": "🎭" }
}

Step 5: 组装数据并生成 HTML

5a. 构建 LOADING_STEPS 数组

反映实际执行的 flyai 搜索命令:

[
  "> flyai search-poi --city-name \"CITY\" --category \"历史古迹\"",
  "  ✓ 发现 N 个文化景点",
  "> flyai keyword-search --query \"CITY 特色体验 演出\"",
  "  ✓ 发现 N 个特色体验",
  "> flyai keyword-search --query \"CITY 美食体验\"",
  "  ✓ 发现 N 个美食推荐",
  "> AI 正在编织叙事故事线...",
  "  ✓ 3 段沉浸式故事已生成",
  "> 意外引擎已就绪"
]

5b. 构建 NODES 数组

3 个节点对象,结构如下:

[
  {
    "id": "{城市拼音}_node1",
    "title": "节点标题(来自产品名或景点名)",
    "chapter": "第一章",
    "story": "Step 3 生成的故事文案",
    "skuName": "飞猪产品名",
    "jumpUrl": "https://...(来自 flyai 搜索结果)",
    "picUrl": "https://...(来自 flyai 搜索结果)",
    "address": "具体地址",
    "reward": { "type": "coupon", "name": "XX体验券", "icon": "🎫" },
    "status": "unlocked",
    "willTriggerRedirect": false,
    "redirectData": null
  },
  {
    "id": "{城市拼音}_node2",
    "title": "节点标题",
    "chapter": "第二章",
    "story": "Step 3 生成的故事文案",
    "skuName": "飞猪产品名",
    "jumpUrl": "https://...",
    "picUrl": "https://...",
    "address": "具体地址",
    "reward": { "type": "coupon", "name": "XX探秘券", "icon": "🎪" },
    "status": "locked",
    "willTriggerRedirect": true,
    "redirectData": {
      "reason": "...",
      "aiThinking": "...",
      "newStory": "...",
      "newSkuName": "...",
      "newJumpUrl": "...",
      "newPicUrl": "...",
      "newAddress": "...",
      "compensationReward": { "type": "coupon", "name": "剧情反转补偿券", "icon": "🎭" }
    }
  },
  {
    "id": "{城市拼音}_node3",
    "title": "节点标题",
    "chapter": "终章",
    "story": "Step 3 生成的故事文案",
    "skuName": "飞猪产品名",
    "jumpUrl": "https://...",
    "picUrl": "https://...",
    "address": "具体地址",
    "reward": { "type": "title", "name": "CITY行者 称号", "icon": "🏆" },
    "status": "locked",
    "willTriggerRedirect": false,
    "redirectData": null
  }
]

5c. 读取模板并替换占位符

⚠️ 强制要求:必须使用 Bash 工具读取 assets/template.html 的完整原始内容,不得从记忆重建或自行编写 HTML。

cat assets/template.html

读取到文件内容后,对以下 7 个占位符做字符串替换,其余所有字符(包括所有