EzEzLife Developers
繁體中文
Bot API
EZLIFE / BOT PLATFORM

Bot API 使用文件

從建立機器人到程式串接

完整介紹 BotMother、訊息介面、驗證碼複製及底部按鈕。範例均使用佔位值。

更新於 HTTPS · JSON · UTF-8

概覽與功能

#

目前公開方法為 getMe、sendMessage、getUpdates。可傳送私聊、群聊文字,接收事件可包含非文字內容。

使用 copy_text 顯示驗證碼複製圖示;使用 reply_markup 下發私聊輸入區下方的文字按鈕。

此網域提供文件;實際 API 仍位於 im.ez7.life 的 /bot-api/ 路徑。收件者可從設定 → 帳號詳情 → ID 複製;收到訊息的 message.chat.id 可直接用於回覆,群聊時是群組 ID。

API 基礎位址
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、狀態、建立時間與 **** 加尾部六碼,並重新簽發或撤銷 Token。機器人數量上限由管理員設定。

指令用途
/start顯示管理說明
/newbot開始建立機器人
/mybots列出自己的機器人
/token @username重新簽發 Token,舊值失效
/revoke @username撤銷目前 Token
/cancel取消建立流程
/help顯示說明

Token 與安全

#

完整 Token 僅於建立或重新簽發時顯示。請當作機器人密碼,保存在後端,勿放在網站、APK、公開儲存庫或日誌。

重新簽發立即使舊 Token 失效;撤銷後停用外部呼叫。遮罩尾碼不能還原 Token,遺失時請重新簽發並更新程式。

舊 Token 的尾碼會在下次合法 Bot API 呼叫時記錄。帳號詳情中的一般 API Token 用於帳號介面;本 Bot API 使用 BotMother 發出的機器人 Token。

getMe · 機器人身分

#
POST/bot-api/<BOT_TOKEN>/getMe

不需要業務參數。回傳機器人 ID、顯示名稱、帳號與群組能力標記,可用來確認 Token。

請求範例
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/getMe" \
  --header 'Content-Type: application/json' --data '{}'
回應範例
{
  "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/bot-api/<BOT_TOKEN>/sendMessage

使用 POST application/json。chat_id 一律為字串,私聊填收件者 ID,群聊填群組 ID 並指定 chat_type: "group"。

文字長度 1–5000 字元,會移除前後空白。請記錄回傳的 message_id;目前沒有冪等鍵,逾時後先確認結果,避免重複傳送。

參數類型必填說明
chat_idstring是收件者 ID,群聊則為群組 ID;一律字串。
textstring是訊息文字,1–5000 字元。
chat_typestring否預設 private;群聊用 group,也接受 supergroup。
copy_textstring否文字中完整出現的 4–12 位數字字串。
reply_markupobject / JSON string否可選的回覆鍵盤物件或 JSON 字串。
請求範例
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
sendMessage · group
{
  "chat_id": "GROUP_ID",
  "chat_type": "group",
  "text": "你的訂單已處理。"
}
回應範例
{
  "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 數字字串,字串可保留開頭的零。

圖示只複製驗證碼。產生、有效期限與驗證由你的系統負責;此欄位只控制顯示與複製。舊用戶端仍可閱讀文字。

sendMessage · copy_text
{
  "chat_id": "USER_ID",
  "text": "範例登入驗證碼:003812,5 分鐘內有效。",
  "copy_text": "003812"
}

底部按鈕鍵盤

#

這是多列文字回覆鍵盤。點擊按鈕會傳送其文字,從 getUpdates 的 message.text 處理業務。

1–8 列、每列 1–4 個、總計最多 24 個按鈕。文字長度 1–64 字元,不可空白、換行或含控制字元。按鈕可為字串或只有 text 的物件。

省略 reply_markup 會保留原鍵盤;新鍵盤會取代舊鍵盤。移除請單獨傳入 remove_keyboard: true。

使用者可收合及展開;一次性鍵盤在按鈕文字傳送成功後收合。新版鍵盤會再展開。狀態按機器人與使用者獨立保存,開啟對話時約每五秒檢查。

鍵盤僅支援機器人私聊,群聊帶 reply_markup 會回傳 422。正文 text 仍必填。布林值須為 JSON true/false,不可傳字串;未知鍵盤或按鈕屬性會拒絕。字串形式的 reply_markup 最多 16 KiB。

參數類型必填說明
reply_markupobject / JSON string否可選的回覆鍵盤物件或 JSON 字串。
keyboardarray[]是二維陣列,每個內層陣列代表一列。
resize_keyboardboolean否預設 true 使用緊湊按鈕;false 使用較高按鈕。
one_time_keyboardboolean否預設 false;true 在按鈕傳送成功後收合。
remove_keyboardboolean否true 清除鍵盤,不可與其他鍵盤欄位並用。
sendMessage · reply_markup
{
  "chat_id": "USER_ID",
  "text": "請選擇服務:",
  "reply_markup": {
    "keyboard": [
      [
        {
          "text": "語言設定"
        },
        {
          "text": "快遞追蹤"
        }
      ],
      [
        {
          "text": "通知設定"
        },
        {
          "text": "輸入單號"
        }
      ]
    ],
    "resize_keyboard": true,
    "one_time_keyboard": false
  }
}
sendMessage · one_time_keyboard
{
  "chat_id": "USER_ID",
  "text": "請選擇服務:",
  "reply_markup": {
    "keyboard": [
      [
        "語言設定",
        "快遞追蹤"
      ]
    ],
    "one_time_keyboard": true
  }
}
sendMessage · remove_keyboard
{
  "chat_id": "USER_ID",
  "text": "操作已完成。",
  "reply_markup": {
    "remove_keyboard": true
  }
}

getUpdates · 接收訊息

#
POST/bot-api/<BOT_TOKEN>/getUpdates

短輪詢:沒有訊息立即回傳空陣列。建議每 1–3 秒一次,失敗後延長間隔。尚未提供公開 webhook 或長輪詢。

處理成功後,把下一個 offset 設為最後 update_id + 1;較早事件會確認消費。失敗保留舊 offset,使用 update_id 或業務鍵去重。

同一機器人的多個消費程式須協調,避免彼此推進 offset。非文字事件查看 content_type 與 raw_content。

參數類型必填說明
offsetinteger否下一個事件 ID,非負整數,預設 0。
limitinteger否每次 1–100 筆,預設 100。
請求範例
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}'
回應範例
{
  "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遞增事件 ID,用於確認消費。
message_id平台訊息 ID。
dateUnix 時間戳記,單位為秒。
chat私聊為發送者 ID;群聊為群組 ID,含 private/group 類型。
from實際發送者 ID 與顯示名稱。
text文字內容;非文字事件可能為空。
content_type內容類型;101 為文字。
raw_content接收圖片、檔案等事件的原始內容。

完整 Python 程式

#

完整範例僅用 Python 標準函式庫,處理 /start、/ping、按鈕文字並保存 offset。請在私有工作目錄執行。

請將範例按鈕處理替換成實際業務;訂單、付款等操作應可去重,再保存消費進度。

bot.py
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

群組與訊息範圍

#

機器人須先加入目標群組。預設需擁有人核准,於機器人對話的更多 → 機器人設定 → 已加入群組檢查邀請。

預設關閉群組隱私模式,啟用接收的群組不必 @ 機器人。自身訊息不進自己的接收佇列;群組生命週期通知不視為一般訊息。

各群接收開關控制事件、自動回覆及轉發。使用者持有的機器人會將啟用群組訊息附群名轉到擁有人私聊;外部存取開關仍控制 API 與事件交付。

機器人設定與自動回覆

#

僅擁有人可設定入群核准、外部存取、各群接收及關鍵字自動回覆。關閉外部存取保留 Token,但會拒絕程式呼叫。

最多 50 條規則,關鍵字 1–100 字元、回覆 1–2000 字元。依序比對、不分大小寫的包含關係,第一條啟用且符合的規則回覆。

自動回覆適用私聊與啟用接收的群組,會避免機器人互相觸發。AI API 串接目前屬規劃功能,尚無公開方法。

錯誤處理

#

JSON 解析失敗或本文過大等前期錯誤使用 errorCode 與 message;其他 API 錯誤使用 error_code 與 description。先檢查 HTTP 狀態與 ok。

回應範例
{
  "ok": false,
  "error_code": 422,
  "description": "chat_id 与 1–5000 字的 text 为必填"
}
狀態處理方式
400JSON 格式無效,請檢查編碼與語法。
401Token 無效、撤銷、重新簽發或機器人停權,請更新憑證。
403擁有人停用外部存取,請至機器人設定開啟。
404檢查 /bot-api/ 路徑、Token 格式與方法名稱。
409同一機器人及使用者的鍵盤正在更新,稍後重試。
413請求本文超過 1 MiB,請縮小。
422修正參數、驗證碼格式或鍵盤結構。
500暫時故障,保留接收 offset 並退避重試;傳送先確認結果。

限制與用戶端相容性

#

文件以目前公開介面為準。尚未公開傳送媒體、inline callback 按鈕、聯絡人或位置請求按鈕,以及 webhook。

底部鍵盤適用新版 Web/H5;內嵌舊 H5 資源的用戶端需更新才能顯示新控制項。

完整請求本文不能超過 1 MiB。依負載控制傳送及輪詢頻率;本指南不承諾固定吞吐量。群組能力標記不取代實際入群、審核與接收權限。