This commit is contained in:
parent
117aa98219
commit
8f8c32a9ec
@ -21,6 +21,8 @@
|
||||
"@types/lodash": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "*"
|
||||
"lodash": "*",
|
||||
"@egg/logger": "^1.4.3",
|
||||
"winston": "*"
|
||||
}
|
||||
}
|
||||
|
167
packages/lark-msg-tool/src/Body/index.ts
Normal file
167
packages/lark-msg-tool/src/Body/index.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { LarkEvent, LarkAction } from "../types"
|
||||
|
||||
class LarkBody {
|
||||
protected body: LarkEvent.Data | LarkAction.Data
|
||||
public isEventMsg: boolean = false
|
||||
public isActionMsg: boolean = false
|
||||
public msgType?: string
|
||||
public userId?: string
|
||||
public msgText: string = ""
|
||||
public chatType?: string
|
||||
public mentions?: any
|
||||
public actionType?: string
|
||||
public actionValue?: any
|
||||
public actionOption?: any
|
||||
public chatId: string = ""
|
||||
public messageId: string = ""
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param body 事件消息体或Action消息体
|
||||
*/
|
||||
constructor(body: LarkEvent.Data | LarkAction.Data) {
|
||||
this.body = body
|
||||
this.isEventMsg = this.getIsEventMsg(body)
|
||||
this.isActionMsg = this.getIsActionMsg(body)
|
||||
|
||||
if (this.isEventMsg) {
|
||||
const eventBody = body as LarkEvent.Data
|
||||
this.msgType = this.getMsgType(eventBody)
|
||||
this.userId = this.getUserId(eventBody)
|
||||
this.msgText = this.getMsgText(eventBody)
|
||||
this.chatType = this.getChatType(eventBody)
|
||||
this.mentions = this.getMentions(eventBody)
|
||||
}
|
||||
|
||||
if (this.isActionMsg) {
|
||||
const actionBody = body as LarkAction.Data
|
||||
this.actionType = this.getActionType(actionBody)
|
||||
this.actionValue = this.getActionValue(actionBody)
|
||||
this.actionOption = this.getActionOption(actionBody)
|
||||
}
|
||||
|
||||
this.chatId = this.getChatId(body)
|
||||
this.messageId = this.getMessageId(body)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为事件消息
|
||||
* @param body 事件消息体
|
||||
* @returns 是否为事件消息
|
||||
*/
|
||||
private getIsEventMsg(body: any): body is LarkEvent.Data {
|
||||
return body?.header?.event_type === "im.message.receive_v1"
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Action消息
|
||||
* @param body Action消息体
|
||||
* @returns 是否为Action消息
|
||||
*/
|
||||
private getIsActionMsg(body: any): body is LarkAction.Data {
|
||||
return !!body?.action
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件文本类型
|
||||
* @param body 事件消息体
|
||||
* @returns 事件文本类型
|
||||
*/
|
||||
private getMsgType(body: LarkEvent.Data): string | undefined {
|
||||
return body?.event?.message?.message_type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户Id
|
||||
* @param body 事件消息体
|
||||
* @returns 用户Id
|
||||
*/
|
||||
private getUserId(body: LarkEvent.Data): string | undefined {
|
||||
return body?.event?.sender?.sender_id?.user_id
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本内容并剔除艾特信息
|
||||
* @param body 事件消息体
|
||||
* @returns 文本内容
|
||||
*/
|
||||
private getMsgText(body: LarkEvent.Data): string {
|
||||
try {
|
||||
const { text }: { text: string } = JSON.parse(body?.event?.message?.content)
|
||||
// 去掉@_user_1相关的内容,例如 '@_user_1 测试' -> '测试'
|
||||
const textWithoutAt = text.replace(/@_user_\d+/g, "")
|
||||
return textWithoutAt
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天类型
|
||||
* @param body 事件消息体
|
||||
* @returns 聊天类型
|
||||
*/
|
||||
private getChatType(body: LarkEvent.Data): string | undefined {
|
||||
return body?.event?.message?.chat_type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取艾特信息
|
||||
* @param body 事件消息体
|
||||
* @returns 艾特信息
|
||||
*/
|
||||
private getMentions(body: LarkEvent.Data): any {
|
||||
return body?.event?.message?.mentions
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action类型
|
||||
* @param body Action消息体
|
||||
* @returns Action类型
|
||||
*/
|
||||
private getActionType(body: LarkAction.Data): string | undefined {
|
||||
return body?.action?.tag
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action参数
|
||||
* @param body Action消息体
|
||||
* @returns Action参数
|
||||
*/
|
||||
private getActionValue(body: LarkAction.Data): any {
|
||||
return body?.action?.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action选项
|
||||
* @param body Action消息体
|
||||
* @returns Action选项
|
||||
*/
|
||||
private getActionOption(body: LarkAction.Data): any {
|
||||
return body?.action?.option
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话流Id
|
||||
* @param body 事件消息体或Action消息体
|
||||
* @returns 对话流Id
|
||||
*/
|
||||
private getChatId(body: LarkEvent.Data | LarkAction.Data): string {
|
||||
if (this.getIsEventMsg(body)) return body?.event?.message?.chat_id
|
||||
if (this.getIsActionMsg(body)) return body?.open_chat_id
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息Id
|
||||
* @param body 事件消息体或Action消息体
|
||||
* @returns 消息Id
|
||||
*/
|
||||
private getMessageId(body: LarkEvent.Data | LarkAction.Data): string {
|
||||
if (this.getIsEventMsg(body)) return body?.event?.message?.message_id
|
||||
if (this.getIsActionMsg(body)) return body?.open_message_id
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
export default LarkBody
|
82
packages/lark-msg-tool/src/Card/component.ts
Normal file
82
packages/lark-msg-tool/src/Card/component.ts
Normal file
@ -0,0 +1,82 @@
|
||||
// 通用备注信息,包含一个纯文本元素,内容中包含作者和请求ID的占位符
|
||||
export const commonNote = {
|
||||
tag: "note",
|
||||
elements: [
|
||||
{
|
||||
tag: "plain_text",
|
||||
content: "${xIcon} 功能由${xAuthor}提供支持,Rid:${requestId}",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// 成功消息的头部信息,包含绿色模板和标题
|
||||
export const successHeader = {
|
||||
template: "green",
|
||||
title: {
|
||||
content: "${xIcon} 感谢使用 ${xName}",
|
||||
tag: "plain_text",
|
||||
},
|
||||
}
|
||||
|
||||
// 错误消息的头部信息,包含红色模板和标题
|
||||
export const errorHeader = {
|
||||
template: "red",
|
||||
title: {
|
||||
content: "⛔ ${xName} 错误提示",
|
||||
tag: "plain_text",
|
||||
},
|
||||
}
|
||||
|
||||
// 待处理消息的头部信息,包含紫色模板和标题
|
||||
export const pendingHeader = {
|
||||
template: "purple",
|
||||
title: {
|
||||
content: "${xIcon} 感谢使用 ${xName}",
|
||||
tag: "plain_text",
|
||||
},
|
||||
}
|
||||
|
||||
// 基础成功卡片,包含markdown内容、水平分割线和通用备注信息
|
||||
export const baseSuccessCard = {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "${content}",
|
||||
},
|
||||
{
|
||||
tag: "hr",
|
||||
},
|
||||
commonNote,
|
||||
],
|
||||
successHeader,
|
||||
}
|
||||
|
||||
// 基础错误卡片,包含markdown内容、水平分割线和通用备注信息
|
||||
export const baseErrorCard = {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "${content}",
|
||||
},
|
||||
{
|
||||
tag: "hr",
|
||||
},
|
||||
commonNote,
|
||||
],
|
||||
errorHeader,
|
||||
}
|
||||
|
||||
// 基础待处理卡片,包含markdown内容、水平分割线和通用备注信息
|
||||
export const basePendingCard = {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: "${content}",
|
||||
},
|
||||
{
|
||||
tag: "hr",
|
||||
},
|
||||
commonNote,
|
||||
],
|
||||
pendingHeader,
|
||||
}
|
201
packages/lark-msg-tool/src/Card/index.ts
Normal file
201
packages/lark-msg-tool/src/Card/index.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import logger from "@egg/logger"
|
||||
import _ from "lodash"
|
||||
import { Logger } from "winston"
|
||||
import { baseErrorCard, basePendingCard, baseSuccessCard } from "./component";
|
||||
|
||||
type FunctionMap = Record<string, { Xname: string; Xauthor: string, Xicon: string }>
|
||||
|
||||
type CardMap = Record<string, any>
|
||||
|
||||
type TempMap = Record<string, string>
|
||||
|
||||
class LarkCard {
|
||||
protected requestId: string
|
||||
protected funcName: string
|
||||
protected functionMap: FunctionMap
|
||||
protected cardMap: CardMap
|
||||
protected tempMap: TempMap
|
||||
protected stringify: boolean
|
||||
protected logger: Logger
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param funcName 函数名
|
||||
* @param stringify 是否序列化为字符串
|
||||
* @param requestId 请求 ID
|
||||
* @param cardMap 卡片映射
|
||||
* @param tempMap 模板映射
|
||||
* @param functionMap 函数映射
|
||||
*/
|
||||
constructor(
|
||||
funcName: string,
|
||||
stringify: boolean,
|
||||
requestId: string,
|
||||
cardMap: CardMap,
|
||||
tempMap: TempMap,
|
||||
functionMap: FunctionMap,
|
||||
) {
|
||||
this.stringify = stringify
|
||||
this.requestId = requestId
|
||||
this.funcName = funcName
|
||||
this.functionMap = functionMap
|
||||
this.cardMap = {
|
||||
success: baseSuccessCard,
|
||||
error: baseErrorCard,
|
||||
pending: basePendingCard,
|
||||
...cardMap,
|
||||
}
|
||||
this.tempMap = tempMap
|
||||
this.logger = logger.child({ requestId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置函数名
|
||||
* @param funcName 函数名
|
||||
*/
|
||||
setFunction(funcName: string) {
|
||||
this.funcName = funcName
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否序列化为字符串
|
||||
* @param stringify 是否序列化为字符串
|
||||
*/
|
||||
setStringify(stringify: boolean) {
|
||||
this.stringify = stringify
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个子卡片
|
||||
* @param func 函数名
|
||||
* @param stringify 是否序列化为字符串
|
||||
* @returns 子卡片实例
|
||||
*/
|
||||
child(func: string, stringify: boolean = true) {
|
||||
return new LarkCard(func, stringify, this.requestId, this.cardMap, this.tempMap, this.functionMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡片消息
|
||||
* @param cardKey 卡片ID
|
||||
* @param variables 变量
|
||||
* @returns 卡片内容或JSON字符串
|
||||
*/
|
||||
genCard(
|
||||
cardKey: keyof typeof this.cardMap,
|
||||
variables: { [key: string]: any },
|
||||
) {
|
||||
const card = this.cardMap[cardKey]
|
||||
if (!card) {
|
||||
this.logger.error(`Card ${cardKey} not found`)
|
||||
throw new Error(`Card ${cardKey} not found`);
|
||||
}
|
||||
const finalVariables: Record<string, any> = {
|
||||
...variables,
|
||||
requestId: this.requestId,
|
||||
...this.functionMap[this.funcName],
|
||||
}
|
||||
this.logger.debug(`Card ${cardKey} final variables: ${JSON.stringify(finalVariables)}`)
|
||||
|
||||
/**
|
||||
* 替换字符串中的变量
|
||||
* @param str 字符串
|
||||
* @returns 替换后的字符串
|
||||
*/
|
||||
const replaceVariables = (str: string): string => {
|
||||
const variablePattern = /\$\{(\w+)\}/g
|
||||
const matches = str.match(variablePattern)
|
||||
|
||||
if (matches && matches.length === 1 && matches[0] === str) {
|
||||
// 如果字符串中只有一个变量,且整个字符串就是这个变量
|
||||
const variableName = matches[0].slice(2, -1) // 去掉 ${ 和 }
|
||||
return finalVariables[variableName] || ""
|
||||
} else {
|
||||
// 否则只替换字符串中的变量部分
|
||||
return str.replace(variablePattern, (_, v) => finalVariables[v] || "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历并替换对象中的变量
|
||||
* @param obj 对象
|
||||
* @returns 替换后的对象
|
||||
*/
|
||||
const traverseAndReplace = (obj: any): any => {
|
||||
if (_.isString(obj)) {
|
||||
return replaceVariables(obj)
|
||||
} else if (_.isArray(obj)) {
|
||||
return obj.map(traverseAndReplace)
|
||||
} else if (_.isObject(obj)) {
|
||||
return _.mapValues(obj, traverseAndReplace)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
const content = traverseAndReplace(card)
|
||||
this.logger.debug(`Card ${cardKey} final content: ${JSON.stringify(content)}`)
|
||||
return this.stringify ? JSON.stringify(content) : content
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成成功卡片
|
||||
* @param content 卡片内容
|
||||
* @returns 成功卡片内容或JSON字符串
|
||||
*/
|
||||
genSuccessCard(content: any) {
|
||||
return this.genCard("success", { content })
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误卡片
|
||||
* @param content 卡片内容
|
||||
* @returns 错误卡片内容或JSON字符串
|
||||
*/
|
||||
genErrorCard(content: any) {
|
||||
return this.genCard("error", { content })
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成待处理卡片
|
||||
* @param content 卡片内容
|
||||
* @returns 待处理卡片内容或JSON字符串
|
||||
*/
|
||||
genPendingCard(content: any) {
|
||||
return this.genCard("pending", { content })
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模板卡片
|
||||
* @param tempKey 模板ID
|
||||
* @param variables 变量
|
||||
* @returns 模板卡片内容或JSON字符串
|
||||
*/
|
||||
genTempCard(tempKey: string, variables: { [key: string]: any }) {
|
||||
const tempId = this.tempMap[tempKey]
|
||||
if (!tempId) {
|
||||
this.logger.error(`Temp ${tempKey} not found`)
|
||||
throw new Error(`Temp ${tempKey} not found`);
|
||||
}
|
||||
const finalVariables: Record<string, any> = {
|
||||
...variables,
|
||||
requestId: this.requestId,
|
||||
...this.functionMap[this.funcName],
|
||||
}
|
||||
this.logger.debug(`Temp ${tempKey} final variables: ${JSON.stringify(finalVariables)}`)
|
||||
const content = {
|
||||
type: "template",
|
||||
data: {
|
||||
config: {
|
||||
update_multi: true,
|
||||
enable_forward: false,
|
||||
},
|
||||
template_id: tempId,
|
||||
template_variable: finalVariables,
|
||||
},
|
||||
}
|
||||
this.logger.debug(`Temp ${tempKey} final content: ${JSON.stringify(content)}`)
|
||||
return this.stringify ? JSON.stringify(content) : content
|
||||
}
|
||||
}
|
||||
|
||||
export default LarkCard
|
@ -2,164 +2,11 @@ import _ from "lodash"
|
||||
|
||||
import { LarkAction, LarkEvent } from "./types"
|
||||
|
||||
/**
|
||||
* 是否为事件消息
|
||||
* @param body 事件消息体
|
||||
* @returns 是否为事件消息
|
||||
*/
|
||||
export const getIsEventMsg = (body: any): body is LarkEvent.Data => {
|
||||
return body?.header?.event_type === "im.message.receive_v1"
|
||||
}
|
||||
import * as CardComponent from "./Card/component"
|
||||
|
||||
/**
|
||||
* 获取事件文本类型
|
||||
* @param body 事件消息体
|
||||
* @returns 事件文本类型
|
||||
*/
|
||||
export const getMsgType = (body: LarkEvent.Data) => {
|
||||
return body?.event?.message?.message_type
|
||||
}
|
||||
import LarkCard from "./Card"
|
||||
|
||||
/**
|
||||
* 获取用户Id
|
||||
* @param body 事件消息体
|
||||
* @returns 用户Id
|
||||
*/
|
||||
export const getUserId = (body: LarkEvent.Data) => {
|
||||
return body?.event?.sender?.sender_id?.user_id
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本内容并剔除艾特信息
|
||||
* @param body 事件消息体
|
||||
* @returns 文本内容
|
||||
*/
|
||||
export const getMsgText = (body: LarkEvent.Data) => {
|
||||
try {
|
||||
const { text }: { text: string } = JSON.parse(body?.event?.message?.content)
|
||||
// 去掉@_user_1相关的内容,例如 '@_user_1 测试' -> '测试'
|
||||
const textWithoutAt = text.replace(/@_user_\d+/g, "")
|
||||
return textWithoutAt
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天类型
|
||||
* @param body 事件消息体
|
||||
* @returns 聊天类型
|
||||
*/
|
||||
export const getChatType = (body: LarkEvent.Data) => {
|
||||
return body?.event?.message?.chat_type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取艾特信息
|
||||
* @param body 事件消息体
|
||||
* @returns 艾特信息
|
||||
*/
|
||||
export const getMentions = (body: LarkEvent.Data) => {
|
||||
return body?.event?.message?.mentions
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Action消息
|
||||
* @param body Action消息体
|
||||
* @returns 是否为Action消息
|
||||
*/
|
||||
export const getIsActionMsg = (body: any): body is LarkAction.Data => {
|
||||
return !!body?.action
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action类型
|
||||
* @param body Action消息体
|
||||
* @returns Action类型
|
||||
*/
|
||||
export const getActionType = (body: LarkAction.Data) => {
|
||||
return body?.action?.tag
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action参数
|
||||
* @param body Action消息体
|
||||
* @returns Action参数
|
||||
*/
|
||||
export const getActionValue = (body: LarkAction.Data) => {
|
||||
return body?.action?.value
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Action选项
|
||||
* @param body Action消息体
|
||||
* @returns Action选项
|
||||
*/
|
||||
export const getActionOption = (body: LarkAction.Data) => {
|
||||
return body?.action?.option
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话流Id
|
||||
* @param body 事件消息体
|
||||
* @returns 对话流Id
|
||||
*/
|
||||
export const getChatId = (body: LarkEvent.Data | LarkAction.Data) => {
|
||||
if (getIsEventMsg(body)) return body?.event?.message?.chat_id
|
||||
if (getIsActionMsg(body)) return body?.open_chat_id
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息Id
|
||||
* @param body 事件消息体
|
||||
* @returns 消息Id
|
||||
*/
|
||||
export const getMessageId = (body: LarkEvent.Data | LarkAction.Data) => {
|
||||
if (getIsEventMsg(body)) return body?.event?.message?.message_id
|
||||
if (getIsActionMsg(body)) return body?.open_message_id
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成卡片消息
|
||||
* @param {any} card - 卡片消息
|
||||
* @param {object} variables - 变量
|
||||
* @param {boolean} [stringify=true] - 是否返回 JSON 字符串
|
||||
*/
|
||||
export const genCardMsg = (
|
||||
card: any,
|
||||
variables: { [key: string]: any },
|
||||
stringify: boolean = true
|
||||
): any => {
|
||||
const replaceVariables = (str: string): string => {
|
||||
const variablePattern = /\$\{(\w+)\}/g
|
||||
const matches = str.match(variablePattern)
|
||||
|
||||
if (matches && matches.length === 1 && matches[0] === str) {
|
||||
// 如果字符串中只有一个变量,且整个字符串就是这个变量
|
||||
const variableName = matches[0].slice(2, -1) // 去掉 ${ 和 }
|
||||
return variables[variableName] || ""
|
||||
} else {
|
||||
// 否则只替换字符串中的变量部分
|
||||
return str.replace(variablePattern, (_, v) => variables[v] || "")
|
||||
}
|
||||
}
|
||||
|
||||
const traverseAndReplace = (obj: any): any => {
|
||||
if (_.isString(obj)) {
|
||||
return replaceVariables(obj)
|
||||
} else if (_.isArray(obj)) {
|
||||
return obj.map(traverseAndReplace)
|
||||
} else if (_.isObject(obj)) {
|
||||
return _.mapValues(obj, traverseAndReplace)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
const updatedCard = traverseAndReplace(card)
|
||||
return stringify ? JSON.stringify(updatedCard) : updatedCard
|
||||
}
|
||||
import LarkBody from "./Body"
|
||||
|
||||
/**
|
||||
* 生成消息卡片的Options
|
||||
@ -174,4 +21,4 @@ export const genCardOptions = (options: Record<string, string>) => {
|
||||
}))
|
||||
}
|
||||
|
||||
export { LarkAction, LarkEvent }
|
||||
export { LarkAction, LarkEvent, CardComponent, LarkCard, LarkBody }
|
||||
|
Loading…
x
Reference in New Issue
Block a user