# EzLife · Bot API 使用文档

https://im-api.ez7.life/zh-hans/

更新于: 2026-11-10

完整说明 BotMother、消息接口、验证码复制与底部按钮。所有示例使用占位值。

## 概览与能力

当前公开方法是 `getMe`、`sendMessage`、`getUpdates`。发送支持私聊/群聊文字，接收事件可包含非文字内容。

通过 `sendMessage` 的 `copy_text` 显示验证码复制图标，通过 `reply_markup` 下发私聊底部文字按钮。

该域名提供文档；实际 API 仍在 im.ez7.life 的 /bot-api/ 路径。接收者 ID 可在设置 → 账号详情 → ID 复制；收到消息的 message.chat.id 可用于回复，群聊时它是群 ID。

```text
https://im.ez7.life/bot-api/<BOT_TOKEN>/<METHOD>
```

## 快速开始

1. 搜索并添加 `@BotMother`，打开对话顶部的机器人管理。
2. 创建机器人、复制 Token，并把它放入后端环境变量 `EZLIFE_BOT_TOKEN`。
3. 先调用 `getMe` 确认身份，再用用户 ID 调用 `sendMessage`。
4. 启动 `getUpdates` 轮询；处理成功后持久化下一次 `offset`。

```bash
python3 bot.py
```

## BotMother 管理

创建时填写 1–64 字显示名称。用户名界面输入 3–28 字前缀，以英文字母开头，仅用字母、数字、下划线；界面补上 `_bot`，先检查可用再创建。

管理页可搜索机器人、查看机器人ID、状态、创建时间与 `****` 加尾6位提示，并重新签发或撤销 Token。数量上限由后台配置。

| 命令 | 作用 |
| --- | --- |
| `/start` | 显示管理说明 |
| `/newbot` | 开始创建机器人 |
| `/mybots` | 列出本人机器人 |
| `/token @username` | 重新签发，旧Token立即失效 |
| `/revoke @username` | 撤销当前Token |
| `/cancel` | 取消创建流程 |
| `/help` | 显示帮助 |

## Token 与安全

完整 Token 仅在创建或重新签发时显示一次；它是机器人密码，只应保存在后端配置。不要放入网页、App、公开仓库或日志。

重新签发会立即使旧 Token 失效；撤销会阻止外部调用。显示尾码不等于可找回完整值，遗失时应重新签发并更新程序。

旧记录若没有尾码，会在下一次合法 Bot API 调用后补记。账号详情里的通用 API Token 用于账号接口；此处 Bot API 使用 BotMother 签发的机器人 Token。

## getMe · 机器人身份

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/getMe`

不需要业务参数，返回机器人ID、显示名称、用户名及群能力标记。请先用此方法检查环境变量和 Token 是否正确。

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/getMe" \
  --header 'Content-Type: application/json' --data '{}'
```

```json
{
  "ok": true,
  "result": {
    "id": "1000000000",
    "is_bot": true,
    "display_name": "Example Bot",
    "username": "example_bot",
    "can_join_groups": true,
    "can_read_all_group_messages": true
  }
}
```

## sendMessage · 发送消息

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/sendMessage`

推荐使用 `POST application/json`。`chat_id` 始终传字符串：私聊是接收用户ID；群聊是群ID，并传 `chat_type: "group"`。

消息正文 1–5000 字，前后空白会被去除。响应中的 `message_id` 可供业务记录；发送接口没有幂等键，超时后先核对结果再决定是否重发。

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `chat_id` | string | 是 | 接收用户ID；群聊时为群ID，始终用字符串。 |
| `text` | string | 是 | 1–5000字正文。 |
| `chat_type` | string | 否 | 默认private；群消息用group，也接受supergroup。 |
| `copy_text` | string | 否 | 正文中完整出现的4–12位数字字符串。 |
| `reply_markup` | object / JSON string | 否 | 可选的底部按钮对象，也可传JSON字符串。 |

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/sendMessage" \
  --header 'Content-Type: application/json' --data-binary @- <<'JSON'
{
  "chat_id": "USER_ID",
  "text": "订单已处理完成。"
}
JSON
```

```json
{
  "chat_id": "GROUP_ID",
  "chat_type": "group",
  "text": "订单已处理完成。"
}
```

```json
{
  "ok": true,
  "result": {
    "message_id": "EXAMPLE_MESSAGE_ID",
    "date": 1794297600,
    "chat": {
      "id": "USER_ID",
      "type": "private"
    },
    "text": "订单已处理完成。"
  }
}
```

发送成功返回 ok: true 与 result；验证码附加 copy_text，菜单附加 reply_markup 和 reply_keyboard_revision。收消息返回 result 数组。没有消息时 result: []。

## 验证码复制

保留正文里的系统名、用途和有效期，再传 `copy_text`。它必须是正文中完整出现的 4–12 位 ASCII 数字字符串；使用字符串保留前导零。

点击图标只复制验证码。此字段负责显示和复制，验证码生成、有效期与验证由你的业务程序处理；旧客户端仍能阅读正文。

```json
{
  "chat_id": "USER_ID",
  "text": "示例系统登录验证码：003812，5分钟内有效。",
  "copy_text": "003812"
}
```

## 底部按钮键盘

按钮是多行文字回复键盘。点击后，用户以普通消息发送按钮文字；程序在 `getUpdates` 的 `message.text` 中处理对应业务。

每行 1–4 个按钮，1–8 行，总计最多24个；每个文字 1–64 字，不允许空白、换行或控制字符。按钮可用字符串或仅含 `text` 的对象。

省略 `reply_markup` 保留旧菜单；发送新 `keyboard` 替换菜单。`remove_keyboard: true` 必须单独使用，移除菜单及展开入口。

用户可收起/展开。一次性键盘仅在按钮消息发送成功后收起，新版本菜单重新展开。菜单按用户和Bot隔离保存；活跃私聊约每5秒核对状态。

键盘仅支持机器人私聊，群聊携带 reply_markup 会返回 422。正文 text 仍必填。布尔值须用 JSON true/false，不能传字符串；未知键盘或按钮属性会拒绝。JSON 字符串形式的 reply_markup 最多 16 KiB。

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `reply_markup` | object / JSON string | 否 | 可选的底部按钮对象，也可传JSON字符串。 |
| `keyboard` | array[] | 是 | 二维数组，每个内部数组是一行按钮。 |
| `resize_keyboard` | boolean | 否 | 默认true，紧凑按钮；false使用更大高度。 |
| `one_time_keyboard` | boolean | 否 | 默认false；true时成功点击发送后收起。 |
| `remove_keyboard` | boolean | 否 | true清除菜单，不能与其他键盘选项组合。 |

```json
{
  "chat_id": "USER_ID",
  "text": "请选择需要的服务：",
  "reply_markup": {
    "keyboard": [
      [
        {
          "text": "语言设置"
        },
        {
          "text": "快递追踪"
        }
      ],
      [
        {
          "text": "通知设置"
        },
        {
          "text": "录入单号"
        }
      ]
    ],
    "resize_keyboard": true,
    "one_time_keyboard": false
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "请选择需要的服务：",
  "reply_markup": {
    "keyboard": [
      [
        "语言设置",
        "快递追踪"
      ]
    ],
    "one_time_keyboard": true
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "本次操作已结束。",
  "reply_markup": {
    "remove_keyboard": true
  }
}
```

## getUpdates · 接收消息

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/getUpdates`

这是短轮询：没有消息时立即返回空数组。建议每 1–3 秒请求，网络失败逐步退避；当前没有对外 Webhook 或长轮询。

业务处理成功后，将下一次 `offset` 设为最后一条 `update_id + 1`。服务器会确认更早更新；处理失败保留原 offset，并按 update_id 或业务编号防重。

同一机器人的程序应协调消费队列，避免多个进程相互推进 offset。非文字事件可以查看 `content_type` 和 `raw_content`。

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `offset` | integer | 否 | 非负整数，下一笔希望读取的更新ID，默认0。 |
| `limit` | integer | 否 | 每次1–100条，默认100。 |

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/getUpdates" \
  --header 'Content-Type: application/json' \
  --data '{"offset": 0, "limit": 100}'
```

```json
{
  "ok": true,
  "result": [
    {
      "update_id": 120,
      "message": {
        "message_id": "EXAMPLE_MESSAGE_ID",
        "date": 1794297600,
        "chat": {
          "id": "1000000001",
          "type": "private"
        },
        "from": {
          "id": "1000000001",
          "display_name": "Example User"
        },
        "text": "语言设置",
        "content_type": 101,
        "raw_content": "{\n  \"content\": \"语言设置\"\n}"
      }
    }
  ]
}
```

| 参数 | 说明 |
| --- | --- |
| `update_id` | 递增更新编号，用于确认消费进度。 |
| `message_id` | 平台消息ID。 |
| `date` | Unix秒时间戳。 |
| `chat` | 私聊发信人ID或群ID，以及private/group类型。 |
| `from` | 实际发信人ID与显示名称。 |
| `text` | 文字内容；非文字事件可能为空。 |
| `content_type` | 消息内容类型，101表示文字。 |
| `raw_content` | 原始消息内容，供处理图片、文件等接收事件。 |

## 完整 Python 程序

下方完整示例只用 Python 标准库，支持 `/start` 显示按钮、`/ping` 回复及按钮文字处理，并持久化 offset。请从程序的私有运行目录启动。

把示例里的按钮分支替换为自己的业务逻辑。涉及订单、付款等操作时，先完成业务幂等处理，再保存消费进度。

```python
import json
import os
import time
import urllib.error
import urllib.request
from pathlib import Path

TOKEN = os.environ["EZLIFE_BOT_TOKEN"]
BASE = "https://im.ez7.life/bot-api/" + TOKEN
OFFSET_FILE = Path("bot-offset.json")
MENU = [
  [
    "语言设置",
    "快递追踪"
  ],
  [
    "通知设置",
    "录入单号"
  ]
]

class ApiFailure(Exception):
    pass

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *args):
        return None

HTTP = urllib.request.build_opener(NoRedirect())

def api(method, payload):
    req = urllib.request.Request(
        BASE + "/" + method,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with HTTP.open(req, timeout=20) as response:
            body = json.load(response)
    except urllib.error.HTTPError as error:
        raise ApiFailure("HTTP " + str(error.code)) from None
    except (urllib.error.URLError, TimeoutError, OSError, ValueError):
        raise ApiFailure("Network or response failure") from None
    if not body.get("ok"):
        raise ApiFailure("API error " + str(body.get("error_code", "unknown")))
    return body["result"]

def handle(update):
    message = update.get("message", {})
    chat = message.get("chat", {})
    text = message.get("text", "")
    if not chat.get("id"):
        return
    payload = {"chat_id": str(chat["id"]),
               "chat_type": chat.get("type", "private")}
    if text == "/start" and payload["chat_type"] == "private":
        payload.update(text="请选择需要的服务：", reply_markup={
            "keyboard": MENU, "resize_keyboard": True,
            "one_time_keyboard": False,
        })
    elif text == "/ping":
        payload["text"] = "pong"
    elif text in [label for row in MENU for label in row]:
        payload["text"] = "本次操作已结束。"
    else:
        return
    api("sendMessage", payload)

def save_offset(value):
    temp = OFFSET_FILE.with_suffix(".tmp")
    fd = os.open(temp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w", encoding="utf-8") as stream:
        json.dump(value, stream)
        stream.flush()
        os.fsync(stream.fileno())
    os.replace(temp, OFFSET_FILE)

def main():
    offset = json.loads(OFFSET_FILE.read_text()) if OFFSET_FILE.exists() else 0
    if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0:
        raise ValueError("Invalid offset file")
    delay = 1
    while True:
        try:
            updates = api("getUpdates", {"offset": offset, "limit": 100})
            for update in updates:
                handle(update)
                next_offset = int(update["update_id"]) + 1
                save_offset(next_offset)
                offset = next_offset
            delay = 1
            time.sleep(2)
        except (ApiFailure, OSError, ValueError):
            print("Processing failed; offset retained. Check configuration.", flush=True)
            time.sleep(delay)
            delay = min(delay * 2, 30)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass
```

## 群组与消息范围

机器人必须先加入目标群。默认需要机器人拥有者同意；在与该Bot的对话右上角更多→机器人设置→加入群组列表中处理待确认邀请。

群隐私模式默认关闭，启用接收后不需要 @Bot。它自己的消息不会回到自己的队列；群系统通知不作为普通消息处理。

每群接收开关影响该群的外部更新、转发和自动回复。用户自建Bot会把启用接收的群消息标注群名转到拥有者私聊；外部访问开关仍控制 API 与更新队列。

## 机器人设置与自动回复

机器人设置仅拥有者可管理：加入审批、外部程序访问、每群接收和关键词自动回复。关闭外部访问后，现有 Token 保留，但程序调用返回拒绝。

自动回复最多50条，关键词1–100字，回复1–2000字；按顺序匹配，不区分大小写的“包含”规则，命中首条已启用规则后回复。

自动回复可用于私聊及启用接收的群；会避免对Bot消息自动连锁回复。AI API接入目前是规划说明，尚无公开调用方法。

## 错误处理

JSON 解析失败或正文过大等早期错误使用 errorCode 与 message；其他 API 错误使用 error_code 与 description。始终先检查 HTTP 状态与 ok。

```json
{
  "ok": false,
  "error_code": 422,
  "description": "chat_id 与 1–5000 字的 text 为必填"
}
```

| 状态 | 处理方式 |
| --- | --- |
| 400 | JSON 格式无效，请检查编码与语法。 |
| 401 | Token无效、已撤销/重新签发或机器人停权；更新密钥后再试。 |
| 403 | 拥有者关闭外部访问；到机器人设置重新开启。 |
| 404 | 检查 /bot-api/ 路径、Token格式和方法名称。 |
| 409 | 同一Bot与用户的菜单正在更新，稍后重试。 |
| 413 | 请求体超过1 MiB，缩小请求内容。 |
| 422 | 修正参数、验证码格式或键盘结构。 |
| 500 | 暂时故障；接收保持offset并退避，发送先核对是否已成功。 |

## 限制与客户端兼容

本页覆盖当前已实现的公开能力。媒体发送、内联 callback 按钮、联系人/位置请求按钮和对外Webhook尚无公开方法。

底部键盘适用于新版 Web/H5；内嵌旧版 H5 资源的客户端需更新才能显示新控件。

完整请求正文不能超过 1 MiB。按实际负载控制发送与轮询频率；本指南没有承诺固定吞吐量。群组能力标记不替代实际入群、审核与接收权限。

