Skip to content

飞书单聊应用机器人

飞书机器人相关介绍

机器人类型

  • 飞书应用机器人支持和用户进行消息交互
  • 飞书自定义机器人仅支持单向往群组内推送消息
  • 飞书自定义机器人就是通过Webhook往群组内推送消息, 场景单一, 集成方便, 适合监控推送类场景.

应用可见范围

配置应用可见范围

  • 授权可以访问机器人应用的部门或成员
  • 修改应用可见范围需要在版本管理与发布中创建一个新版本

机器人添加步骤

  1. 开发者后台创建应用, 并开启机器人能力

单聊机器人权限开通

权限管理入口: 飞书开放平台 -> 选择目标应用 -> 左侧权限管理 -> 开通权限

需要开通以下三个权限

  • im:message
  • im:message.p2p_msg:readonly
  • im:message:send_as_bot

API

机器人主动向会话发送消息

  1. 发送消息

需要注意, receive_id_type 需要根据实际情况.

在单聊机器人中, 推荐使用open_id.

open_id的值可以在接收消息的监听事件data.event.sender.sender_id.open_id 中获得.

def push_text_message(
    appid: str = typer.Option(..., help="飞书应用ID", envvar="FEISHU_APP_ID"),
    appsecret: str = typer.Option(..., help="飞书应用密钥", envvar="FEISHU_APP_SECRET"),
    log_level: int = typer.Option(logging.DEBUG, help="日志级别", envvar="FEISHU_LOG_LEVEL"),
    receive_id_type: Literal["open_id", "union_id", "user_id", "email", "chat_id"] = typer.Option("open_id", help="和 receive_id 一起使用"),
    receive_id: str = typer.Option(..., help="和 receive_id_type 一起使用"),
    msg_type: Literal["text", "post", "image", "file", "audio", "media", "sticker", "interactive", "share_chat", "share_user", "system"] = typer.Option("text", help="和 content 一起使用"),
    content: str = typer.Argument(..., help="推送文本消息"),
):
    """
    飞书推送文本消息.

    https://open.feishu.cn/document/server-docs/im-v1/message/create
    """
    logging.basicConfig(level=log_level)
    typer.echo(helptext)
    client = init_client(appid, appsecret)
    if client.im is None:
        raise Exception("client.im is None")
    if client.im.v1 is None:
        raise Exception("client.im.v1 is None")
    request: CreateMessageRequest = (
        CreateMessageRequest.builder()
        .receive_id_type(receive_id_type)
        .request_body(CreateMessageRequestBody.builder().receive_id(receive_id).msg_type(msg_type).content(json.dumps({"text": content})).build())
        .build()
    )

    # 发起请求
    response: CreateMessageResponse = client.im.v1.message.create(request)
)

机器人接收用户消息并回复

  1. 接收消息
  2. 回复消息

示例代码

客户端实例化

存在两个实例化的客户端, 一个是wsClient, 负责维护 WebSocket 长连接, 一个client, 负责调用飞书 OpenAPI

def init_ws_client(appid: str, appsecret: str) -> lark.ws.Client:
    global client
    event_handler = lark.EventDispatcherHandler.builder("", "").register_p2_im_message_receive_v1(do_p2_im_message_receive_v1).build()

    client = lark.Client.builder().app_id(appid).app_secret(appsecret).build()
    wsClient = lark.ws.Client(
        appid,
        appsecret,
        event_handler=event_handler,
        log_level=lark.LogLevel.DEBUG,
    )
    return wsClient
注册监听事件
# https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/events/receive
# https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create
def do_p2_im_message_receive_v1(data: P2ImMessageReceiveV1) -> None:
    global client
    if client is None:
        raise Exception("client is None")
    if client.im is None:
        raise Exception("client.im is None")
    res_content = ""
    if data.event is None or data.event.message is None or data.event.sender is None or data.event.sender.sender_id is None or data.event.sender.sender_id.open_id is None:
        typer.echo("解析消息失败, 请发送文本消息")
        raise Exception("解析消息失败, 请发送文本消息")
    if data.event.message.message_type == "text" and data.event.message.content is not None:
        text = json.loads(data.event.message.content)["text"]
        res_content = (
            f"{text}\nchat_type: {data.event.message.chat_type}\nchat_id: {data.event.message.chat_id}"
            f"\nmessage_type: {data.event.message.message_type}\nmessage_id: {data.event.message.message_id}"
            f"\nsender_openid: {data.event.sender.sender_id.open_id}\nsender_type: {data.event.sender.sender_type}"
        )
        typer.echo(f"res_content: {res_content}")
    else:
        res_content = "解析消息失败, 请发送文本消息"

    content = json.dumps({"text": "收到你发送的消息:" + res_content})
    if data.event.message.chat_type == "p2p":
        receive_id = data.event.message.chat_id
        if receive_id is None:
            raise Exception("receive_id is None")
        p2p_request: CreateMessageRequest = (
            CreateMessageRequest.builder().receive_id_type("chat_id").request_body(CreateMessageRequestBody.builder().receive_id(receive_id).msg_type("text").content(content).build()).build()
        )

        response: CreateMessageResponse = client.im.v1.message.create(p2p_request)

        if not response.success():
            raise Exception(f"client.im.v1.message.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}")
    else:
        msg_id = data.event.message.message_id
        if msg_id is None:
            raise Exception("msg_id is None")
        other_request: ReplyMessageRequest = ReplyMessageRequest.builder().message_id(msg_id).request_body(ReplyMessageRequestBody.builder().content(content).msg_type("text").build()).build()
        reply_response: ReplyMessageResponse = client.im.v1.message.reply(other_request)
        if not reply_response.success():
            raise Exception(f"client.im.v1.message.reply failed, code: {reply_response.code}, msg: {reply_response.msg}, log_id: {reply_response.get_log_id()}")
启动客户端
wsClient = init_ws_client(appid, appsecret)
wsClient.start()

WebSocket VS Webhook

飞书机器人应用允许以WebSecket 长连接或 Webhook 事件推送的方式接收用户消息.

WebSocket 模式免去了公网端口依赖, 便于开发调试和部署.

WebSocket
Webhook
  • Webhook 需要应用有暴露在公网的地址, 并配置在飞书开发者后台
  • Webhook模式需要应用在 1 秒内回复请求中携带的challenge参数

相关链接