logo
/
飞书 ChatGPT 机器人
Get a copy
2.1k
用 JavaScript 五分钟开发一个飞书 ChatGPT 机器人
  • readme.md
    arrow

    用 JavaScript 开发飞书 ChatGPT 机器人(含全部源码,免费托管,手把手教程)

    本文帮助你快速实现一个飞书对话机器人,并在其中接入 ChatGPT 的能力,可以直接问它问题,也可以在群聊天中 at 它,返回 ChatGPT 的回答。(以下为效果截图)

    通过本文你将学会

    1. 创建飞书机器人,并配置事件和权限
    2. 使用 AirCode 的「一键 Copy 代码」功能,实现机器人的聊天能力
    3. 将机器人接入 ChatGPT 能力

    第一步:创建飞书机器人

    1. 飞书开发者后台中创建一个应用,并且添加机器人能力。

    1. 创建好的机器人就有 App ID 和 App Secret,可以复制备用。

    第二步:创建 AirCode 应用

    1. 通过 AirCode 源码链接中右上角的「Get a copy」按钮快速生成一个自己的 AirCode Node.js 应用。 注意不要直接复制代码,如果是直接复制纯代码粘贴过去,需要再手工安装 NPM 依赖包。 如果没有登录,需先登录 AirCode。

    1. 飞书开发者后台中刚创建应用的「凭证与基础信息」页面中的 App ID 和 App Secret,粘贴到刚创建的 AirCode 应用的环境变量(Environments)中,在 feishuAppId 和 feishuAppSecret 中分别填入粘贴过来的机器人 App ID 和 App Secret 的值。

    1. 配置好环境变量(Environments)后,点击页面上方的「Deploy 按钮」部署整个应用,使所有配置生效。

    第三步:配置机器人的事件和权限

    1. AirCode 应用部署成功后,选择调用文件 chat.js,你就能看到当前服务的调用 URL,将它复制,填到飞书开发者后台刚刚创建机器人的「事件订阅-请求地址」中。 注意不要开启 Encrypt Key。

    1. 点击保存时,如果失败,可以将 AirCode 应用再次部署。

    1. 给机器人添加「接收消息」的事件

    1. 对应事件需要开启相应权限
      • 获取用户在群组中@机器人的消息 im:message.group_at_msg(注意不要开启「获取群组中所有消息」的权限)
      • 获取用户发给机器人的单聊消息 im:message.p2p_msg
      • 获取与发送单聊、群组消息 im:message
      • 以应用的身份发消息 im:message:send as bot

    1. 配置好了之后,需要发布机器人才能生效,并且能够搜索到。 注意如果想加到群中让更多人看到,在发布机器人时需要选择更大的「可见范围」。

    第四步:测试聊天机器人

    1. 将机器人发布后,可在聊天窗口中与机器人私聊,或者将机器人加入到群中 at 机器人聊天,此时机器人可以对话。由于还没有配置 ChatGPT 能力,所以机器人会直接将你的消息返回,这时表示机器人已经配置成功。

    1. 可以在 AirCode 中查看完整的请求数据,并且使用线上 request 调试代码。

    第五步:接入 ChatGPT 能力

    1. OpenAI 的控制台中,点「Create new secret key」生成并且复制这个新生成的 Key,粘贴到刚创建的 AirCode 应用的环境变量(Environments)中,粘贴到 OpenAISecret 的 value 中。如果没有 OpenAI 账号,可以到网络中搜索一下获取方式,提前购买准备好。

    1. 再次部署服务,后测试。目前 ChatGPT 服务比较慢,尤其是模型版本越高级、问题越复杂,ChatGPT 服务的返回时间会越长。

    问题反馈

    更多阅读

  • chat.js
    arrow
    1// 引入基础依赖包
    2const aircode = require('aircode');
    3const axios = require('axios');
    4
    5// 引入 OpenAI 的 SDK
    6const openai = require('openai');
    7
    8// 从环境变量中获取 OpenAI 的 Secret
    9const OpenAISecret = process.env.OpenAISecret;
    10let chatGPT = null;
    11if (OpenAISecret) {
    12  // 与 ChatGTP 聊天的方法,传入字符串即可
    13  const configuration = new openai.Configuration({ apiKey: OpenAISecret });
    14  const client = new openai.OpenAIApi(configuration);
    15  chatGPT = async (content) => {
    16    return await client.createChatCompletion({
    17      // 使用当前 OpenAI 开放的最新 3.5 模型,如果后续 4 发布,则修改此处参数即可
    18      // OpenAI models 参数列表 https://platform.openai.com/docs/models
    19      model: 'gpt-3.5-turbo',
    20      // 让 ChatGPT 充当的角色为 assistant
    21      messages: [{ role: 'assistant', content }],
    22    });
    23  };
    24}
    25
    26// 从环境变量中获取飞书机器人的 App ID 和 App Secret
    27const feishuAppId = process.env.feishuAppId;
    28const feishuAppSecret = process.env.feishuAppSecret;
    29
    30// 获取飞书 tenant_access_token 的方法
    31const getTenantToken = async () => {
    32  const url =
    33    'https://open.feishu.cn/open-apis/v3/auth/tenant_access_token/internal/';
    34  const res = await axios.post(url, {
    35    app_id: feishuAppId,
    36    app_secret: feishuAppSecret,
    37  });
    38  return res.data.tenant_access_token;
    39};
    40
    41// 用飞书机器人回复用户消息的方法
    42const feishuReply = async (objs) => {
    43  const tenantToken = await getTenantToken();
    44  const url = `https://open.feishu.cn/open-apis/im/v1/messages/${objs.msgId}/reply`;
    45  let content = objs.content;
    46
    47  // 实现 at 用户能力
    48  if (objs.openId) content = `<at user_id="${objs.openId}"></at> ${content}`;
    49  const res = await axios({
    50    url,
    51    method: 'post',
    52    headers: { Authorization: `Bearer ${tenantToken}` },
    53    data: { msg_type: 'text', content: JSON.stringify({ text: content }) },
    54  });
    55  return res.data.data;
    56};
    57
    58// 飞书 ChatGPT 机器人的入口函数
    59module.exports = async function (params, context) {
    60  // 判断是否开启了事件 Encrypt Key,如果开启提示错误
    61  if (params.encrypt)
    62    return { error: '请在飞书机器人配置中移除 Encrypt Key。' };
    63
    64  // 用来做飞书接口校验,飞书接口要求有 challenge 参数时需直接返回
    65  if (params.challenge) return { challenge: params.challenge };
    66
    67  // 判断是否没有开启事件相关权限,如果没有开启,则返回错误
    68  if (!params.header || !params.header.event_id) {
    69    // 判断当前是否为通过 Debug 环境触发
    70    if (context.trigger === 'DEBUG') {
    71      return {
    72        error:
    73          '如机器人已配置好,请先通过与机器人聊天测试,再使用「Mock by online requests」功能调试。',
    74      };
    75    } else {
    76      return {
    77        error:
    78          '请参考教程配置好飞书机器人的事件权限,相关权限需发布机器人后才能生效。',
    79      };
    80    }
    81  }
    82
    83  // 所有调用当前函数的参数都可以直接从 params 中获取
    84  // 飞书机器人每条用户消息都会有 event_id
    85  const eventId = params.header.event_id;
    86
    87  // 可以使用数据库极其简单地写入数据到数据表中
    88  // 实例化一个名字叫做 contents 的表
    89  const contentsTable = aircode.db.table('contents');
    90
    91  // 搜索 contents 表中是否有 eventId 与当前这次一致的
    92  const contentObj = await contentsTable.where({ eventId }).findOne();
    93
    94  // 如果 contentObj 有值,则代表这条 event 出现过
    95  // 由于 ChatGPT 返回时间较长,这种情况可能是飞书系统的重试,直接 return 掉,防止重复调用
    96  // 当当前环境为 DEBUG 环境时,这条不生效,方便调试
    97  if (contentObj && context.trigger !== 'DEBUG') return;
    98  const message = params.event.message;
    99  const msgType = message.message_type;
    100
    101  // 获取发送消息的人信息
    102  const sender = params.event.sender;
    103
    104  // 用户发送过来的内容
    105  let content = '';
    106
    107  // 返回给用户的消息
    108  let replyContent = '';
    109
    110  // 目前 ChatGPT 仅支持文本内容
    111  if (msgType === 'text') {
    112    // 获取用户具体消息,机器人默认将收到的消息直接返回
    113    content = JSON.parse(message.content).text;
    114
    115    // 如果是 at 所有人,则不处理
    116    if (content.indexOf('@_all') >= 0) return;
    117
    118    // 获取用户发送的内容实体,去掉 at 符号等
    119    content = content.replace('@_user_1 ', '');
    120
    121    // 默认将用户发送的内容回复给用户,仅是一个直接返回对话的机器人
    122    replyContent = content;
    123
    124    // 将消息体信息储存到数据库中,以备后续查询历史或做上下文支持使用
    125    await contentsTable.save({
    126      eventId: params.header.event_id,
    127      msgId: message.message_id,
    128      openId: sender.sender_id.open_id,
    129      content,
    130    });
    131
    132    // 如果配置了 OpenAI Key 则让 ChatGPT 回复
    133    if (OpenAISecret) {
    134      // 将用户具体消息发送给 ChatGPT
    135      try {
    136        const result = await chatGPT(content);
    137
    138        // 将获取到的 ChatGPT 回复给用户
    139        replyContent = `${result.data.choices[0].message.content.trim()}`;
    140      } catch (err) {
    141        err = err.toJSON();
    142        if (err.status === 401)
    143          return {
    144            error:
    145              'OpenAI ChatGPT 返回 401 错误,应该是你配置错了 OpenAI 平台的 Secret Token,请重新配置。',
    146          };
    147        if (err.status === 429)
    148          return {
    149            error:
    150              'OpenAI ChatGPT 的 API 返回 429 错误,可能是你的额度已耗尽,你需要去 OpenAI 付费充值你的 API 配额。付费购买的 ChatGPT Plus 仅是网页版,API 需要另外充值。',
    151          };
    152        return err.message;
    153      }
    154    }
    155  } else {
    156    replyContent = '不好意思,暂时不支持其他类型的文件。';
    157  }
    158
    159  // 将处理后的消息通过飞书机器人发送给用户
    160  await feishuReply({
    161    msgId: message.message_id,
    162    openId: sender.sender_id.open_id,
    163    content: replyContent,
    164  });
    165
    166  // 整个函数调用结束,需要有返回
    167  return null;
    168};
    169
  • Runtime
    arrow
    • Node.js versionnode/v16
    • Function execution timeout90 s
  • Dependencies
    arrow
    • aircode0.2.12
    • axios1.3.4
    • openai3.2.1
  • Environments
    arrow
    arrow
    • feishuAppId
    • feishuAppSecret
    • OpenAISecret