robot.router.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. const Router = require('@koa/router');
  2. const lark = require('@larksuiteoapi/node-sdk');
  3. const config = require('../config');
  4. const request = require('../utils/request');
  5. const util = require('../utils/util');
  6. const router = new Router({ prefix: '/robot' });
  7. /**
  8. * 飞书机器人服务接口
  9. * 主要用于发送飞书消息、创建知识库
  10. */
  11. // 获取租户token
  12. async function getTenantToken() {
  13. try {
  14. if (!config.FEISHU_APP_ID || !config.FEISHU_APP_SECRET) return '';
  15. const response = await request.post(
  16. 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal',
  17. JSON.stringify({
  18. app_id: config.FEISHU_APP_ID,
  19. app_secret: config.FEISHU_APP_SECRET,
  20. }),
  21. );
  22. const res = JSON.parse(response.data);
  23. if (res.code !== 0) return '';
  24. return res.tenant_access_token;
  25. } catch (error) {
  26. return '';
  27. }
  28. }
  29. // 获取租户token
  30. router.get('/getTenantToken', async (ctx) => {
  31. const token = await getTenantToken();
  32. if (!token) return util.fail(ctx, '获取飞书token失败');
  33. util.success(ctx, { token });
  34. });
  35. // 发送卡片消息
  36. router.post('/sendMessage', async (ctx) => {
  37. const token = await getTenantToken();
  38. if (!token) return util.fail(ctx, '获取飞书token失败');
  39. const { msgType, content, receiveId, templateId, variables, appId, appSecret } = ctx.request.body;
  40. if (!msgType || (msgType !== 'text' && msgType !== 'interactive')) {
  41. return ctx.throw(400, '消息类型错误,仅支持text和interactive');
  42. }
  43. if (msgType === 'text') {
  44. if (!content) return ctx.throw(400, '发送内容不能为空');
  45. } else {
  46. if (!templateId) {
  47. return ctx.throw(400, '发送模板不能为空');
  48. }
  49. }
  50. if (!receiveId) {
  51. return ctx.throw(400, '接收群组ID不能为空');
  52. }
  53. if (!appId || !appSecret) {
  54. return ctx.throw(400, '请先配置飞书appId或appSecret');
  55. }
  56. const client = new lark.Client({
  57. appId,
  58. appSecret,
  59. disableTokenCache: true,
  60. });
  61. if (msgType === 'text') {
  62. const res = await client.im.message.create(
  63. {
  64. params: {
  65. receive_id_type: 'chat_id',
  66. },
  67. data: {
  68. receive_id: receiveId,
  69. content: JSON.stringify({ text: content }),
  70. msg_type: 'text',
  71. },
  72. },
  73. lark.withTenantToken(token),
  74. );
  75. if (res.code === 0) {
  76. util.success(ctx, '发送成功');
  77. } else {
  78. util.fail(ctx, res.msg);
  79. }
  80. } else {
  81. const res = await client.im.message.createByCard(
  82. {
  83. params: {
  84. receive_id_type: 'chat_id', //chat_id
  85. },
  86. data: {
  87. receive_id: receiveId,
  88. msg_type: msgType,
  89. template_id: templateId,
  90. template_variable: variables || {},
  91. },
  92. },
  93. lark.withTenantToken(token),
  94. );
  95. if (res.code === 0) {
  96. util.success(ctx, '发送成功');
  97. } else {
  98. util.fail(ctx, res.msg);
  99. }
  100. }
  101. });
  102. // 获取机器人所在群列表
  103. router.get('/chat/groups', async (ctx) => {
  104. const token = await getTenantToken();
  105. if (!token) return util.fail(ctx, '获取飞书token失败');
  106. try {
  107. const response = await request.get('https://open.feishu.cn/open-apis/im/v1/chats', '', {
  108. headers: {
  109. Authorization: `Bearer ${token}`,
  110. },
  111. });
  112. const res = JSON.parse(response.data);
  113. if (res.code === 0) {
  114. util.success(ctx, res.data);
  115. } else {
  116. util.fail(ctx, res.msg);
  117. }
  118. } catch (error) {
  119. util.fail(ctx, error);
  120. }
  121. });
  122. // 创建知识库副本
  123. router.post('/createNode', async (ctx) => {
  124. const token = await getTenantToken();
  125. if (!token) return util.fail(ctx, '获取飞书token失败');
  126. const { spaceId, nodeToken, title } = ctx.request.body;
  127. if (!spaceId || isNaN(spaceId) || !nodeToken) {
  128. return ctx.throw(400, '空间ID或节点token不能为空');
  129. }
  130. if (!title) {
  131. return ctx.throw(400, '知识库标题不能为空');
  132. }
  133. try {
  134. const response = await request.post(
  135. `https://open.feishu.cn/open-apis/wiki/v2/spaces/${spaceId}/nodes/${nodeToken}/copy`,
  136. JSON.stringify({
  137. target_space_id: parseInt(spaceId),
  138. target_parent_token: nodeToken,
  139. title,
  140. }),
  141. {
  142. headers: {
  143. Authorization: `Bearer ${token}`,
  144. 'Content-Type': 'application/json',
  145. },
  146. },
  147. );
  148. const res = JSON.parse(response.data);
  149. if (res.code === 0) {
  150. util.success(ctx, res.data);
  151. } else {
  152. util.fail(ctx, res.msg);
  153. }
  154. } catch (error) {
  155. util.fail(ctx, error);
  156. }
  157. });
  158. module.exports = router;