import { parseJsonString } from "@egg/hooks" import { LarkEvent } from "@egg/lark-msg-tool" import { Lark } from "@egg/net-tool" import { Context } from "../../types" import MapPolyfill from "../../utils/polyfill/map" import SetPolyfill from "../../utils/polyfill/set" interface Message { user: string content: string time: string } /** * 提取 JSON 数据中的文本内容 * @param data - JSON 数据 * @returns 提取的文本内容 */ const extractTextFromJson = (data: any): string => { let result = "" if (Array.isArray(data)) { // 如果是数组,遍历数组元素 for (const element of data) { result += extractTextFromJson(element) // 递归调用处理每个元素 } } else if (typeof data === "object" && data !== null) { // 如果是对象,遍历对象的键 for (const key in data) { if (key === "text" && typeof data[key] === "string") { result += data[key] // 拼接 text 值 } else { result += extractTextFromJson(data[key]) // 递归调用处理子对象 } } } return result } /** * 获取聊天历史记录 * @param context - 上下文数据 * @returns 聊天消息数组 */ const getChatHistory = async ( { larkService, logger }: Context, { chatId, startTime, endTime, senderOpenId, mentions: targetUsers, excludedMessageIds = [], excludeMentions = [], }: { chatId: string startTime: string endTime: string senderOpenId?: string mentions?: LarkEvent.Mention[] excludedMessageIds?: string[] // 不需要的消息 ID excludeMentions?: string[] // 不需要的提到的人,可以是openid,也可以是userName } ): Promise<{ messages: Message[] mentions: MapPolyfill }> => { // 获取服务器的时区偏移量(以分钟为单位) const serverTimezoneOffset = new Date().getTimezoneOffset() // 上海时区的偏移量(UTC+8,以分钟为单位) const shanghaiTimezoneOffset = -8 * 60 // 将startTime和endTime转换为时间戳,并调整为上海时区 const startTimeTimestamp = Math.round( new Date(startTime).getTime() / 1000 + (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 ) const endTimeTimestamp = Math.round( new Date(endTime).getTime() / 1000 + (shanghaiTimezoneOffset - serverTimezoneOffset) * 60 ) // 获取群聊中的历史记录 const { data: chatHistory } = await larkService.message.getHistory( chatId, String(startTimeTimestamp), String(endTimeTimestamp) ) if (!chatHistory?.length) return { messages: [], mentions: new MapPolyfill(), } const targetUsersSet = new SetPolyfill( targetUsers ?.filter?.((user) => user.id.user_id) ?.map?.((user) => user.id.open_id) ?? [] ) // 清洗数据 // 取出所有的被AT的人,以及发送者 const mentions: MapPolyfill = new MapPolyfill() const senders: SetPolyfill = new SetPolyfill() // 先把提问者加进去 if (senderOpenId) senders.add(senderOpenId) // 过滤出文本和post消息 const allowedMsgTypes = ["text", "post"] const filteredMsg: typeof chatHistory = [] // 遍历历史消息 for (const chat of chatHistory) { let hasExcludeMention = false if (chat.mentions) { for (const mention of chat.mentions) { mentions.set(mention.id, mention.name) // 过滤掉不需要的提到的人 if ( excludeMentions.includes(mention.id) || excludeMentions.includes(mention.name) ) { hasExcludeMention = true } } } // 如果提到了不要包含的人,则跳过该消息 if (hasExcludeMention) { continue } // 过滤掉不需要的消息 ID if (excludedMessageIds.includes(chat.message_id)) { continue } // 过滤掉不是文本和post消息的消息 if (!allowedMsgTypes.includes(chat.msg_type)) { continue } // 过滤掉不是用户发送的消息 if (!chat.sender || chat.sender.sender_type !== "user") { continue } // 如果目标用户集合不为空,并且消息的发送者不在目标用户集合中,并且消息中提到的所有用户都不在目标用户集合中,则跳过该消息 if ( targetUsersSet.size !== 0 && !targetUsersSet.has(chat.sender.id) && (!chat.mentions || chat.mentions.every((mention) => !targetUsersSet.has(mention.id))) ) { continue } senders.add(chat.sender.id) filteredMsg.push(chat) } // 取出没有被AT的发送者 const noMentionSenders = new SetPolyfill( [...senders].filter((sender) => !mentions.has(sender)) ) logger.debug("获取聊天消息提及用户信息", { mentions: [...mentions.entries()], }) logger.debug("获取消息发送者信息", { senders: [...senders] }) logger.debug("获取未被提及的发送者信息", { noMentionSenders: [...noMentionSenders], }) // 从接口获取用户名 if (noMentionSenders.size !== 0) { try { const { data: { items }, } = await larkService.user.batchGet([...noMentionSenders]) logger.debug("获取用户详细信息", { items }) for (const item of items) { mentions.set(item.open_id, item.name) } } catch (error) { // 报错了可以不处理,只是没有名字而已 logger.error("获取用户信息失败", { error }) } } const messages: Message[] = [] /** * 获取文本消息内容 * @param chat - 聊天消息数据 * @returns 文本消息内容 */ const getText = (chat: Lark.MessageData) => { let { text } = parseJsonString(chat.body.content, { text: "" }) as { text: string } if (!text) return "" // 替换被AT的人 if (chat.mentions) { for (const mention of chat.mentions) { const mentionKey = mention.key const mentionName = `@${mention.name}` text = text.replace(mentionKey, mentionName) } } // 去除可能出现的标签 return text.replace(/<[^>]+>/g, "") } /** * 获取 post 消息内容 * @param chat - 聊天消息数据 * @returns post 消息内容 */ const getPost = (chat: Lark.MessageData) => { const content = parseJsonString(chat.body.content, null) if (!content) return "" return extractTextFromJson(content) } // 构建消息数组 for (const chat of filteredMsg) { // 过滤掉机器人消息 const user = mentions.get(chat.sender.id) if (!user) continue messages.push({ user: mentions.get(chat.sender.id)!, content: chat.msg_type === "text" ? getText(chat) : getPost(chat), time: new Date(Number(chat.create_time)).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", }), }) } return { messages, mentions, } } export default getChatHistory