小游戏(Games)
小游戏让 Bot 在会话中发出一种特殊的「游戏卡片」。用户点击卡片上的按钮后,客户端会在内置 WebView 中打开一个 HTML5 游戏;游戏结束时通过 Bot API 回传分数,并把高分榜直接展示在卡片上。
小游戏基于 SafeW 的 HTML5 Games 协议,与 Mini App 是两套独立的能力:游戏运行在普通网页中,不依赖 Mini App 的全屏、安全区、主按钮等 WebApp 接口。
准备工作
- 通过 @BotFather 的
/newbot创建一个 Bot 并获取 Token(详见快速开始)。 - 通过 @BotFather 的
/newgame为该 Bot 注册一个游戏,得到游戏的short_name(见下文)。 - 把游戏页面部署到一个可公网访问的 HTTPS 地址。
创建游戏(/newgame)
向 @BotFather 发送 /newgame,按提示依次录入:
| 步骤 | 说明 |
|---|---|
| 选择 Bot | 选择由哪个 Bot 提供该游戏 |
| 标题 Title | 游戏名称 |
| 描述 Description | 游戏的简短介绍 |
| 封面图 Photo | 游戏卡片封面,建议 640×360 |
| 演示动图 GIF | 可选,发送 /empty 跳过 |
| 游戏地址 URL | 游戏的 HTTPS 地址 |
| 短名 short_name | 游戏的唯一标识,3–30 个字符,仅含 a-z A-Z 0-9 _ |
创建成功后,short_name 即可作为 sendGame 的 game_short_name 使用。同一个 Bot 下 short_name 必须唯一。
工作流程
1. 发送游戏卡片
Bot ── sendGame{chat_id, game_short_name} ──▶ 会话中出现游戏卡片
2. 用户打开游戏
用户点击卡片上的游戏按钮
→ Bot 收到一条 callback_query(携带 game_short_name,无 data)
→ Bot 用 answerCallbackQuery{url} 返回游戏地址
→ 客户端在 WebView 中打开该地址
3. 回传分数
游戏内 ── setGameScore{user_id, score, …} ──▶ 服务端记录分数
→ 默认自动编辑原卡片,刷新高分榜
4. 查看高分榜
Bot/客户端 ── getGameHighScores{user_id, …} ──▶ 返回高分榜游戏按钮(callback_game)
游戏卡片上的按钮是一个特殊的内联键盘按钮:它带有 callback_game 字段,点击后客户端会发起游戏回调(而不是普通的 callback_data 回调)。
- 调用 sendGame 时,如果不传
reply_markup,服务端会自动追加一个「播放 + 游戏标题」按钮。 - 如果自定义
reply_markup,第一行的第一个按钮必须是callback_game按钮,用于启动游戏。
{
"inline_keyboard": [
[{ "text": "▶️ 开始游戏", "callback_game": {} }],
[{ "text": "玩法说明", "url": "https://example.com/how-to-play" }]
]
}callback_game 当前是一个占位对象,无需填写任何字段。
打开游戏(处理游戏回调)
当用户点击游戏按钮时,Bot 会通过 getUpdates 或 Webhook 收到一条 callback_query。游戏回调的特征是 game_short_name 非空、且不含 data 字段,据此可与普通的 callback_data 回调区分。
CallbackQuery 的主要字段:
| 字段 | 类型 | 描述 |
|---|---|---|
| id | String | 回调查询的唯一标识符,应答时回传 |
| from | User | 触发回调的用户 |
| message | Message | 携带游戏按钮的消息(消息载体时存在) |
| inline_message_id | String | 内联消息标识符(内联载体时存在) |
| data | String | 普通回调数据;游戏回调时不含此字段 |
| game_short_name | String | 触发回调的游戏短名;用于识别游戏回调 |
Bot 收到后,应调用 answerCallbackQuery 并在 url 中返回游戏的打开地址:
curl -X POST "https://api.safew.bot/<token>/answerCallbackQuery" \
-H "Content-Type: application/json" \
-d '{
"callback_query_id": "<callback_query_id>",
"url": "https://example.com/game?session=<signed_token>"
}'建议在 url 中附带一次性、带签名和有效期的会话参数(标识 user_id、所在会话/消息、过期时间),供游戏前端在调用 setGameScore 时定位实例并防止伪造分数。
平台也可由服务端直接下发已签名的游戏地址(无需 Bot 往返)。因此
answerCallbackQuery{url}是标准且通用的接入方式,但并非唯一方式。
提交与展示分数
- 游戏结束后,用 setGameScore 提交分数。默认情况下,新分数必须严格大于该用户的现有分数,否则会被拒绝;可用
force=true强制覆盖。 - 提交成功后默认会自动编辑原游戏卡片,刷新其上的高分榜;传
disable_edit_message=true可关闭此行为。 - 用 getGameHighScores 拉取高分榜。
数据类型
Game
游戏卡片中携带的游戏对象。
| 字段 | 类型 | 描述 |
|---|---|---|
| title | String | 游戏标题 |
| description | String | 游戏描述 |
| photo | PhotoSize[] | 游戏封面图,不同尺寸的数组 |
| text | String | 可选,游戏卡片中的简介文本,0–4096 个字符 |
| text_entities | MessageEntity[] | 可选,简介文本中的特殊实体(如用户名、URL 等) |
| animation | Animation | 可选,展示在游戏卡片中的演示动图 |
GameHighScore
高分榜中的一条记录。
| 字段 | 类型 | 描述 |
|---|---|---|
| position | Integer | 名次(从 1 开始) |
| user | User | 该名次对应的用户 |
| score | Integer | 该用户的分数 |
CallbackGame
游戏按钮的占位对象,当前不包含任何字段。
方法一览
| 方法 | 描述 | HTTP 方法 |
|---|---|---|
| sendGame | 发送游戏卡片 | POST |
| setGameScore | 提交/更新用户分数 | POST |
| getGameHighScores | 获取游戏高分榜 | POST |
相关方法:answerCallbackQuery(处理游戏回调、返回游戏地址)。
