# EzLife · Tài liệu Bot API

https://im-api.ez7.life/vi/

Cập nhật: 2026-11-10

Hướng dẫn đầy đủ về BotMother, tin nhắn, sao chép mã xác minh và bàn phím trả lời. Ví dụ dùng giá trị mẫu.

## Tổng quan và tính năng

API công khai có `getMe`, `sendMessage`, `getUpdates`. Có thể gửi văn bản riêng hoặc trong nhóm và nhận sự kiện chứa cả nội dung phi văn bản.

Dùng `copy_text` để hiện biểu tượng sao chép mã; dùng `reply_markup` để hiển thị nút văn bản dưới ô soạn tin riêng.

Tên miền này lưu tài liệu. API vẫn tại im.ez7.life dưới /bot-api/. Sao chép ID người nhận ở Cài đặt → Chi tiết tài khoản → ID. Dùng message.chat.id nhận được để trả lời; sự kiện nhóm chứa ID nhóm.

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

## Bắt đầu nhanh

1. Tìm và thêm `@BotMother`, rồi mở quản lý bot ở đầu cuộc trò chuyện.
2. Tạo bot, sao chép Token và lưu vào biến môi trường phía máy chủ `EZLIFE_BOT_TOKEN`.
3. Gọi `getMe` để kiểm tra danh tính, sau đó gửi `sendMessage` với ID người nhận.
4. Thăm dò `getUpdates`; chỉ lưu offset tiếp theo sau khi xử lý thành công.

```bash
python3 bot.py
```

## Quản lý bằng BotMother

Tên hiển thị dài 1–64 ký tự. Tiền tố tên tài khoản trong biểu mẫu dài 3–28 ký tự, bắt đầu bằng chữ cái, chỉ gồm chữ cái, số và gạch dưới; biểu mẫu thêm `_bot`. Hãy kiểm tra tên còn trống.

Trang quản lý cho phép tìm bot, xem ID, trạng thái, ngày tạo và `****` kèm 6 ký tự cuối Token; cấp lại hoặc thu hồi Token. Quản trị viên đặt hạn mức số bot.

| Lệnh | Chức năng |
| --- | --- |
| `/start` | Hiện hướng dẫn quản lý |
| `/newbot` | Bắt đầu tạo bot |
| `/mybots` | Liệt kê bot của bạn |
| `/token @username` | Cấp lại Token, vô hiệu Token cũ |
| `/revoke @username` | Thu hồi Token hiện tại |
| `/cancel` | Hủy quá trình tạo |
| `/help` | Hiện trợ giúp |

## Token và bảo mật

Token đầy đủ chỉ hiện khi tạo hoặc cấp lại. Đây là mật khẩu của bot: lưu ở máy chủ, không đưa vào trang web, gói ứng dụng, kho công khai hay nhật ký.

Cấp lại làm Token cũ mất hiệu lực ngay; thu hồi chặn truy cập bên ngoài. Phần đuôi che giấu không thể khôi phục Token. Nếu mất, cấp lại và cập nhật chương trình.

Phần đuôi Token cũ được ghi lại khi gọi Bot API hợp lệ tiếp theo. Token API tài khoản trong chi tiết tài khoản dùng cho API tài khoản; Bot API này dùng Token do BotMother cấp.

## getMe · Danh tính bot

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

Không cần tham số nghiệp vụ. Trả về ID bot, tên hiển thị, tên tài khoản và cờ hỗ trợ nhóm. Dùng để kiểm tra 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 · Gửi tin nhắn

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

Dùng `POST application/json`. Luôn gửi `chat_id` dạng chuỗi: ID người nhận cho tin riêng, ID nhóm và `chat_type: "group"` cho nhóm.

Văn bản dài 1–5000 ký tự, bỏ khoảng trắng hai đầu. Lưu message_id trả về. Chưa có khóa chống gửi lặp; khi hết thời gian chờ hãy kiểm tra kết quả trước khi gửi lại.

| Tham số | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `chat_id` | string | Có | ID người nhận hoặc ID nhóm, luôn là chuỗi. |
| `text` | string | Có | Văn bản 1–5000 ký tự. |
| `chat_type` | string | Không | Mặc định private; nhóm dùng group, cũng chấp nhận supergroup. |
| `copy_text` | string | Không | Chuỗi 4–12 chữ số xuất hiện nguyên vẹn trong text. |
| `reply_markup` | object / JSON string | Không | Đối tượng bàn phím hoặc chuỗi JSON, không bắt buộc. |

```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": "Đơn hàng của bạn đã được xử lý."
}
JSON
```

```json
{
  "chat_id": "GROUP_ID",
  "chat_type": "group",
  "text": "Đơn hàng của bạn đã được xử lý."
}
```

```json
{
  "ok": true,
  "result": {
    "message_id": "EXAMPLE_MESSAGE_ID",
    "date": 1794297600,
    "chat": {
      "id": "USER_ID",
      "type": "private"
    },
    "text": "Đơn hàng của bạn đã được xử lý."
  }
}
```

Gửi thành công trả ok: true và result, thêm copy_text cho mã; reply_markup và reply_keyboard_revision cho menu. Nhận trả mảng result, không có sự kiện là result: [].

## Sao chép mã xác minh

Giữ tên hệ thống, mục đích và thời hạn trong tin nhắn rồi thêm copy_text: chuỗi 4–12 chữ số ASCII xuất hiện nguyên vẹn trong text. Dùng chuỗi để giữ số 0 đầu.

Biểu tượng chỉ sao chép mã. Ứng dụng của bạn tạo mã, kiểm tra và áp dụng thời hạn; trường này chỉ điều khiển hiển thị và sao chép. Ứng dụng cũ vẫn đọc được tin.

```json
{
  "chat_id": "USER_ID",
  "text": "Mã đăng nhập mẫu: 003812. Hiệu lực 5 phút.",
  "copy_text": "003812"
}
```

## Bàn phím trả lời

Đây là bàn phím văn bản nhiều hàng. Nhấn nút gửi nhãn như tin nhắn thường; xử lý qua message.text của getUpdates.

Có 1–8 hàng, 1–4 nút mỗi hàng, tổng tối đa 24 nút. Nhãn 1–64 ký tự, không rỗng, xuống dòng hay ký tự điều khiển. Nút là chuỗi hoặc đối tượng chỉ chứa text.

Bỏ reply_markup giữ bàn phím cũ; bàn phím mới thay bàn phím cũ. Gửi riêng remove_keyboard: true để xóa bàn phím và nút mở.

Người dùng có thể thu gọn hoặc mở. Bàn phím một lần chỉ ẩn sau khi gửi nút thành công; phiên bản mới tự mở. Trạng thái riêng cho từng bot/người dùng, kiểm tra khoảng 5 giây khi trò chuyện đang hoạt động.

Bàn phím chỉ cho tin riêng; gửi reply_markup vào nhóm trả 422. text vẫn bắt buộc. Dùng true/false JSON, không dùng chuỗi. Thuộc tính bàn phím/nút lạ bị từ chối. reply_markup dạng chuỗi JSON tối đa 16 KiB.

| Tham số | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `reply_markup` | object / JSON string | Không | Đối tượng bàn phím hoặc chuỗi JSON, không bắt buộc. |
| `keyboard` | array[] | Có | Mảng hai chiều, mỗi mảng con là một hàng. |
| `resize_keyboard` | boolean | Không | Mặc định true cho nút gọn; false cho nút cao hơn. |
| `one_time_keyboard` | boolean | Không | Mặc định false; true ẩn sau khi gửi nút thành công. |
| `remove_keyboard` | boolean | Không | true xóa bàn phím, không kết hợp tùy chọn khác. |

```json
{
  "chat_id": "USER_ID",
  "text": "Chọn dịch vụ:",
  "reply_markup": {
    "keyboard": [
      [
        {
          "text": "Ngôn ngữ"
        },
        {
          "text": "Theo dõi bưu kiện"
        }
      ],
      [
        {
          "text": "Thông báo"
        },
        {
          "text": "Nhập mã vận đơn"
        }
      ]
    ],
    "resize_keyboard": true,
    "one_time_keyboard": false
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "Chọn dịch vụ:",
  "reply_markup": {
    "keyboard": [
      [
        "Ngôn ngữ",
        "Theo dõi bưu kiện"
      ]
    ],
    "one_time_keyboard": true
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "Thao tác đã hoàn tất.",
  "reply_markup": {
    "remove_keyboard": true
  }
}
```

## getUpdates · Nhận tin nhắn

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

Đây là thăm dò ngắn: không có tin sẽ trả mảng rỗng ngay. Gọi mỗi 1–3 giây, tăng thời gian chờ khi lỗi. Chưa hỗ trợ webhook công khai hoặc thăm dò dài.

Sau xử lý thành công, đặt offset = update_id cuối + 1; các sự kiện trước đó được xác nhận. Nếu lỗi giữ offset cũ, chống lặp bằng update_id hoặc khóa nghiệp vụ.

Phối hợp các tiến trình dùng chung bot để tránh tăng offset của nhau. Xem content_type và raw_content cho sự kiện phi văn bản.

| Tham số | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `offset` | integer | Không | ID sự kiện tiếp theo không âm, mặc định 0. |
| `limit` | integer | Không | 1–100 sự kiện mỗi yêu cầu, mặc định 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": "Ngôn ngữ",
        "content_type": 101,
        "raw_content": "{\n  \"content\": \"Ngôn ngữ\"\n}"
      }
    }
  ]
}
```

| Tham số | Mô tả |
| --- | --- |
| `update_id` | ID tăng dần để xác nhận xử lý. |
| `message_id` | ID tin nhắn trên nền tảng. |
| `date` | Thời gian Unix tính bằng giây. |
| `chat` | ID người gửi khi tin riêng hoặc ID nhóm, kèm loại private/group. |
| `from` | ID và tên hiển thị của người gửi thực tế. |
| `text` | Nội dung văn bản; có thể rỗng khi phi văn bản. |
| `content_type` | Loại nội dung; 101 là văn bản. |
| `raw_content` | Nội dung gốc của sự kiện ảnh, tệp và loại khác. |

## Chương trình Python đầy đủ

Ví dụ đầy đủ chỉ dùng thư viện chuẩn Python, xử lý /start, /ping và nhãn nút, lưu offset. Chạy trong thư mục riêng.

Thay xử lý nút mẫu bằng nghiệp vụ thực tế. Đảm bảo đơn hàng, thanh toán có khả năng chống lặp trước khi lưu tiến độ.

```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 = [
  [
    "Ngôn ngữ",
    "Theo dõi bưu kiện"
  ],
  [
    "Thông báo",
    "Nhập mã vận đơn"
  ]
]

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="Chọn dịch vụ:", 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"] = "Thao tác đã hoàn tất."
    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
```

## Nhóm và phạm vi tin nhắn

Bot phải tham gia nhóm trước. Mặc định cần chủ sở hữu duyệt; vào cuộc trò chuyện của bot → Thêm → Cài đặt bot → Nhóm đã tham gia để duyệt.

Mặc định chế độ riêng tư nhóm tắt: nhóm bật nhận tin không cần @bot. Tin do chính bot gửi không vào hàng đợi của nó; thông báo vòng đời nhóm không phải tin thường.

Công tắc nhận của từng nhóm kiểm soát sự kiện, chuyển tiếp và tự trả lời. Bot thuộc người dùng chuyển tin nhóm đã bật tới tin riêng của chủ kèm tên nhóm. Quyền truy cập bên ngoài vẫn kiểm soát API và nhận sự kiện.

## Cài đặt và trả lời tự động

Chỉ chủ sở hữu quản lý duyệt vào nhóm, truy cập bên ngoài, nhận theo nhóm và quy tắc từ khóa. Tắt truy cập bên ngoài giữ Token nhưng từ chối gọi API.

Tối đa 50 quy tắc; từ khóa 1–100 ký tự, câu trả lời 1–2000. So khớp chứa, không phân biệt hoa thường, theo thứ tự; quy tắc bật đầu tiên khớp sẽ trả lời.

Tự trả lời hỗ trợ tin riêng và nhóm bật nhận, tránh chuỗi bot trả lời nhau. Tích hợp AI API đang được dự kiến, chưa có phương thức công khai.

## Xử lý lỗi

Lỗi đầu vào sớm như JSON sai hoặc nội dung quá lớn dùng errorCode và message. Lỗi API khác dùng error_code và description. Luôn kiểm tra HTTP và ok trước.

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

| Trạng thái | Cách xử lý |
| --- | --- |
| 400 | JSON không hợp lệ. Kiểm tra mã hóa và cú pháp. |
| 401 | Token sai, đã thu hồi/cấp lại hoặc bot bị khóa. Cập nhật thông tin xác thực. |
| 403 | Chủ sở hữu tắt truy cập bên ngoài. Bật trong cài đặt bot. |
| 404 | Kiểm tra đường dẫn /bot-api/, định dạng Token và tên phương thức. |
| 409 | Bàn phím bot/người dùng đang được cập nhật. Thử lại sau ít giây. |
| 413 | Nội dung yêu cầu quá 1 MiB. Giảm kích thước. |
| 422 | Sửa tham số, định dạng mã hoặc cấu trúc bàn phím. |
| 500 | Lỗi tạm thời. Giữ offset và lùi thời gian gọi; kiểm tra kết quả gửi trước khi thử lại. |

## Giới hạn và hỗ trợ ứng dụng

Hướng dẫn theo API hiện tại. Chưa có API gửi ảnh/tệp, nút callback inline, yêu cầu danh bạ/vị trí hoặc webhook công khai.

Bàn phím cần Web/H5 hiện hành; ứng dụng chứa H5 cũ cần cập nhật để hiển thị điều khiển mới.

Toàn bộ yêu cầu tối đa 1 MiB. Điều chỉnh tần suất theo tải thực tế; hướng dẫn không cam kết thông lượng cố định. Cờ khả năng nhóm không thay thế tư cách thành viên, phê duyệt và quyền nhận.

