diff --git a/README.md b/README.md index f666b69..e0f1397 100644 --- a/README.md +++ b/README.md @@ -734,6 +734,7 @@ npm install n8n-nodes-wecom - ✅ [分配客服会话](https://developer.work.weixin.qq.com/document/path/94669) - ✅ [发送消息](https://developer.work.weixin.qq.com/document/path/94677) - ✅ [发送欢迎语等事件响应消息](https://developer.work.weixin.qq.com/document/path/95122) +- ✅ [读取消息](https://developer.work.weixin.qq.com/document/path/94670) - ✅ [「升级服务」配置](https://developer.work.weixin.qq.com/document/path/94674) - ✅ [获取客户基础信息](https://developer.work.weixin.qq.com/document/path/95159) diff --git a/nodes/WeCom/resources/kf/execute.ts b/nodes/WeCom/resources/kf/execute.ts index c8cd9e4..2e4cc46 100644 --- a/nodes/WeCom/resources/kf/execute.ts +++ b/nodes/WeCom/resources/kf/execute.ts @@ -146,6 +146,74 @@ export async function executeKf( open_kfid, upgrade_config: parsedConfig, }); + } else if (operation === 'syncMsg') { + const open_kfid = this.getNodeParameter('open_kfid', i) as string; + const cursor = this.getNodeParameter('cursor', i, '') as string; + const token = this.getNodeParameter('token', i, '') as string; + const limit = this.getNodeParameter('limit', i, 100) as number; + const voice_format = this.getNodeParameter('voice_format', i, 0) as number; + const parse_message_types = this.getNodeParameter('parse_message_types', i, true) as boolean; + + const body: IDataObject = { + open_kfid, + limit, + voice_format, + }; + + if (cursor) body.cursor = cursor; + if (token) body.token = token; + + response = await weComApiRequest.call(this, 'POST', '/cgi-bin/kf/sync_msg', body); + + // 如果需要解析消息类型,对msg_list进行处理 + if (parse_message_types && response.msg_list && Array.isArray(response.msg_list)) { + response.msg_list = (response.msg_list as IDataObject[]).map((msg: IDataObject) => { + const msgtype = msg.msgtype as string; + + // 为每个消息添加解析后的字段,便于后续处理 + const parsedMsg: IDataObject = { + ...msg, + parsed_content: null, + }; + + // 根据消息类型解析内容 + if (msgtype === 'text' && msg.text) { + parsedMsg.parsed_content = msg.text; + } else if (msgtype === 'image' && msg.image) { + parsedMsg.parsed_content = msg.image; + } else if (msgtype === 'voice' && msg.voice) { + parsedMsg.parsed_content = msg.voice; + } else if (msgtype === 'video' && msg.video) { + parsedMsg.parsed_content = msg.video; + } else if (msgtype === 'file' && msg.file) { + parsedMsg.parsed_content = msg.file; + } else if (msgtype === 'location' && msg.location) { + parsedMsg.parsed_content = msg.location; + } else if (msgtype === 'link' && msg.link) { + parsedMsg.parsed_content = msg.link; + } else if (msgtype === 'business_card' && msg.business_card) { + parsedMsg.parsed_content = msg.business_card; + } else if (msgtype === 'miniprogram' && msg.miniprogram) { + parsedMsg.parsed_content = msg.miniprogram; + } else if (msgtype === 'msgmenu' && msg.msgmenu) { + parsedMsg.parsed_content = msg.msgmenu; + } else if (msgtype === 'channels_shop_product' && msg.channels_shop_product) { + parsedMsg.parsed_content = msg.channels_shop_product; + } else if (msgtype === 'channels_shop_order' && msg.channels_shop_order) { + parsedMsg.parsed_content = msg.channels_shop_order; + } else if (msgtype === 'merged_msg' && msg.merged_msg) { + parsedMsg.parsed_content = msg.merged_msg; + } else if (msgtype === 'channels' && msg.channels) { + parsedMsg.parsed_content = msg.channels; + } else if (msgtype === 'event' && msg.event) { + parsedMsg.parsed_content = msg.event; + // 为事件类型添加event_type字段方便筛选 + parsedMsg.event_type = (msg.event as IDataObject).event_type; + } + + return parsedMsg; + }); + } } else if (operation === 'getCustomerInfo') { const open_kfid = this.getNodeParameter('open_kfid', i) as string; const external_userid = this.getNodeParameter('external_userid', i) as string; diff --git a/nodes/WeCom/resources/kf/index.ts b/nodes/WeCom/resources/kf/index.ts index 00d22a8..fa2fc96 100644 --- a/nodes/WeCom/resources/kf/index.ts +++ b/nodes/WeCom/resources/kf/index.ts @@ -16,6 +16,7 @@ import { listServicerDescription } from './listServicer'; import { transServiceStateDescription } from './transServiceState'; import { sendKfMsgDescription } from './sendKfMsg'; import { sendKfEventMsgDescription } from './sendKfEventMsg'; +import { syncMsgDescription } from './syncMsg'; import { setUpgradeServiceDescription } from './setUpgradeService'; import { getCustomerInfoDescription } from './getCustomerInfo'; @@ -100,6 +101,11 @@ export const kfDescription: INodeProperties[] = [ value: 'sendKfEventMsg', action: '发送事件响应消息', }, + { + name: '读取消息', + value: 'syncMsg', + action: '读取消息', + }, { name: '设置升级服务配置', value: 'setUpgradeService', @@ -149,6 +155,7 @@ export const kfDescription: INodeProperties[] = [ ...transServiceStateDescription, ...sendKfMsgDescription, ...sendKfEventMsgDescription, + ...syncMsgDescription, ...setUpgradeServiceDescription, ...getCustomerInfoDescription, // 统计管理 diff --git a/nodes/WeCom/resources/kf/syncMsg.ts b/nodes/WeCom/resources/kf/syncMsg.ts new file mode 100644 index 0000000..e053b1b --- /dev/null +++ b/nodes/WeCom/resources/kf/syncMsg.ts @@ -0,0 +1,94 @@ +import type { INodeProperties } from 'n8n-workflow'; + +const showOnlyForSyncMsg = { + resource: ['kf'], + operation: ['syncMsg'], +}; + +export const syncMsgDescription: INodeProperties[] = [ + { + displayName: '客服账号 Name or ID', + name: 'open_kfid', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getKfAccounts', + }, + required: true, + displayOptions: { + show: showOnlyForSyncMsg, + }, + default: '', + description: + 'Choose from the list, or specify an ID using an expression', + hint: '客服账号', + }, + { + displayName: '游标', + name: 'cursor', + type: 'string', + displayOptions: { + show: showOnlyForSyncMsg, + }, + default: '', + description: '上一次调用的next_cursor,第一次拉取可以不填。持久化保存游标可以实现增量拉取。', + hint: '增量拉取的游标,第一次可不填', + }, + { + displayName: 'Token', + name: 'token', + type: 'string', + displayOptions: { + show: showOnlyForSyncMsg, + }, + default: '', + description: '回调事件返回的token字段,10分钟内有效。可选,如果不填接口有严格的频率限制。', + hint: '回调事件的token,可选', + }, + { + displayName: '拉取条数', + name: 'limit', + type: 'number', + displayOptions: { + show: showOnlyForSyncMsg, + }, + default: 100, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: '期望请求的数据量,默认100,最大1000。实际返回可能小于设定值。', + hint: '期望拉取的消息数量,最大1000', + }, + { + displayName: '语音格式', + name: 'voice_format', + type: 'options', + displayOptions: { + show: showOnlyForSyncMsg, + }, + options: [ + { + name: 'AMR', + value: 0, + }, + { + name: 'SILK', + value: 1, + }, + ], + default: 0, + description: '语音消息类型,0-Amr 1-Silk,默认0。可根据需求选择合适的语音格式。', + hint: '语音消息的格式', + }, + { + displayName: '解析消息类型', + name: 'parse_message_types', + type: 'boolean', + displayOptions: { + show: showOnlyForSyncMsg, + }, + default: true, + description: '是否自动解析并展开不同的消息类型到单独的字段,便于后续处理', + hint: '自动解析消息类型', + }, +];