From 52f93752d8e9db43f6fc84dc7c1221ec11c222c8 Mon Sep 17 00:00:00 2001 From: atorber Date: Mon, 18 Dec 2023 03:05:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BC=98=E5=8C=96readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 54 +++++++++++++++++++++---------------------- src/app/extract-at.ts | 2 +- src/app/qa.ts | 40 ++++++++++++++++++++------------ 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 6e0020ae..718fa9d4 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,19 @@ # ChatFlow -GitHub stars badge GitHub forks badge GitHub license badge [![NPM Version](https://img.shields.io/npm/v/@atorber/chatflow?color=brightgreen)](https://www.npmjs.com/package/@atorber/chatflow) +GitHub stars badge GitHub forks badge [![NPM Version](https://img.shields.io/npm/v/@atorber/chatflow?color=brightgreen)](https://www.npmjs.com/package/@atorber/chatflow) ![npm downloads](https://img.shields.io/npm/dm/@atorber/chatflow.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/atorber/chatflow) - ![Docker Image Size](https://img.shields.io/docker/image-size/atorber/chatflow/latest) ![Docker Stars](https://img.shields.io/docker/stars/atorber/chatflow) + ![Docker Image Size](https://img.shields.io/docker/image-size/atorber/chatflow/latest) ![Docker Stars](https://img.shields.io/docker/stars/atorber/chatflow) GitHub license badge ## 简介 ChatFlow是一个聊天机器人管理系统,可以帮助你实现一些原生IM无法支持的功能。 -如果你是一个社群工作者、拼团团长、业务群运营经理,使用这个项目可以帮助你解决一些实际工作中的问题。 +如果你是一个社群工作者、微信群私域运营人员,使用这个项目可以帮助你解决一些自动化问题。 -此外还提供定时任务、消息存档等功能。 +基于开源项目Wechaty实现,目前主要验证了对WeChat的支持,理论上支持钉钉、飞书、whatsapp等Wechaty已实现的所有IM。 -基于开源项目Wechaty实现,目前主要验证了对微信的支持,理论上支持微信、钉钉、飞书、whatsapp等。 - -已适配网页版微信,支持在Linux、Mac、Windows上运行。 +支持在Linux、Mac、Windows上运行。 [访问项目语雀文档查看完整使用说明](https://www.yuque.com/atorber/chatflow) @@ -23,14 +21,16 @@ ChatFlow是一个聊天机器人管理系统,可以帮助你实现一些原生 |功能|描述| |--|--| +|定时提醒|定时消息发送,支持单次定时和周期消息发送给指定好友或群| +|智能问答|可以自定义问答内容,智能匹配答案,支持相似问题匹配,例如“什么时候到货?”“亲,几时到货”“亲,什么时候到货”均能匹配(基于微信对话开放平台,免费)| +|ChatGPT问答|已对接ChatGPT,支持使用ChatGPT作为聊天机器人呢| |群发通知|向群或好友批量下发消息| |消息存档|群聊天消息存档到表格(基于vika维格表,免费),可以在维格表中对聊天消息进行进一步统计、筛选、分析等| -|定时提醒|定时消息发送,支持单次定时和周期消息发送给指定好友或群| |活动报名|群内接龙报名,使用 报名/取消 指令统计活动报名| -|智能问答|可以自定义问答内容,智能匹配答案,支持相似问题匹配,例如“什么时候到货?”“亲,几时到货”“亲,什么时候到货”均能匹配(基于微信对话开放平台,免费)| -|白名单|支持配置群白名单,白名单内群开启机器人问答/活动报名,未配置问题答案的群不会受到机器人干扰| +|白名单|支持配置群白名单,白名单内群开启机器人问答/活动报名/ChatGPT问答,未配置问题答案的群不会受到机器人干扰| |MQTT消息推送|支持配置一个MQTTQ消息队列,将消息推送到队列当中| |远程控制发消息|支持通过MQTT控制机器人向指定好友或群发消息| +|Web控制台|Web端控制台预览版已上线,抢险体验可以访问 https://chat.vlist.cc| > 移步语雀文档查看 [详细功能查看](https://www.yuque.com/atorber/oegota/aialc7sbyb4ldmg4/edit) @@ -71,20 +71,18 @@ npm run start 5.开启智能问答功能 -5.1 设置微信对话平台token,填写"环境变量"表中的 【对话平台token】、【对话平台EncodingAESKey】并在"功能开关"表中开启智能问答 +5.1 设置微信对话平台token,填写【环境变量|Env】表中的 【微信对话开放平台-Token】、【微信对话开放平台-EncodingAESKey】、【微信对话开放平台-APPID】、【微信对话开放平台-管理员ID】并将【智能问答-启用自动问答】修改为 true -添加一个简单问题到微信对话开放平台,测试对应群内智能问答内容 +5.2 添加问题到【问答列表|Qa】,添加之后在管理员群内发送【更新问答】 -5.2 如果不希望每个群都开启智能问答,需设置群白名单,首先需要将上图中的群白名单开关设置为开启 +5.3 将群加入到【白名单|WhiteList】,在【白名单|WhiteList】表中,所属应用选择【智能问答|qa】 -然后将群加入到问答白名单,在“群白名单”表中,加入需要开启的群ID(roomid) +> 群ID在消息中查看(在群里发一条消息,然后控制台查看或在维格表中查找) -群ID在消息中查看(在群里发一条消息,然后控制台查看或在维格表中查找) +5.4 在管理群发送【更新白名单】或者重启程序 详细操作参考 [手把手教程](https://www.yuque.com/atorber/oegota/zm4ulnwnqp9whmd6) -5.3 重启程序,在指定群测试问答 - ## 在Docker中部署运行 注意,因为wechaty-puppet-xp必须依赖Windows微信客户端,所以不能使用Docker,但使用wechaty-puppet-padlocal、wechaty-puppet-service则可以用Doker来部署, @@ -93,17 +91,6 @@ npm run start > 移步语雀文档查看 [手把手教程](https://www.yuque.com/atorber/oegota/zm4ulnwnqp9whmd6) -### Wechaty-Puppet支持 - -|puppet名称|支持平台 |需要token |付费| 备注| -|--|--|--|--|--| -|wechaty-puppet-wechat| Windows、Linux、macOS |否| 否 |网页版wechat,无法获取真实的微信ID和群ID,重启之后ID可能会变| -|wechaty-puppet-xp|Windows| 否| 否 |仅支持windows| -|wechaty-puppet-padlocal👍| Windows、Linux、macOS| 是 |是 | -|wechaty-puppet-service👍| Windows、Linux、macOS| 是 |是 |企业微信| - -> 特别注意,Wechaty-Puppet是wechaty的概念,本项目不涉及机器人开发,只是使用wechaty项目进行业务功能实现,什么是[Wechaty](https://wechaty.js.org/)请点击链接进行了解学习 - ### 拉取和运行 - 最新版本 @@ -116,6 +103,17 @@ docker run -d --restart=always atorber/chatflow:latest ``` +## Wechaty-Puppet支持 + +|puppet名称|支持平台 |需要token |付费| 备注| +|--|--|--|--|--| +|wechaty-puppet-wechat| Windows、Linux、macOS |否| 否 |网页版wechat,无法获取真实的微信ID和群ID,重启之后ID可能会变| +|wechaty-puppet-xp|Windows| 否| 否 |仅支持windows| +|wechaty-puppet-padlocal👍| Windows、Linux、macOS| 是 |是 | +|wechaty-puppet-service👍| Windows、Linux、macOS| 是 |是 |企业微信| + +> 特别注意,Wechaty-Puppet是wechaty的概念,本项目不涉及机器人开发,只是使用wechaty项目进行业务功能实现,什么是[Wechaty](https://wechaty.js.org/)请点击链接进行了解学习 + ## 视频演示及使用教程 到项目官网 [查看视频教程](https://qabot.vlist.cc/) diff --git a/src/app/extract-at.ts b/src/app/extract-at.ts index 05354f09..138d1d18 100644 --- a/src/app/extract-at.ts +++ b/src/app/extract-at.ts @@ -57,7 +57,7 @@ export const extractAtContent = async (message: Message): Promise p = p + chatText - p = `微信群聊天记录:\n${chatText}\n\n指令:\n你是微信聊天群里的成员【${keyWord}】,你正在参与大家的群聊天,先在轮到你发言了,你的回复尽可能清晰、严谨,字数不超过150字,并且你需要使用${role}的风格回复。当前时间是${time}。` + p = `微信群聊天记录:\n${chatText}\n\n指令:\n你是微信聊天群里的成员【${keyWord}】,你正在参与大家的群聊天,现在轮到你发言了,你的回复尽可能清晰、严谨,字数不超过150字,并且你需要使用${role}的风格回复。当前时间是${time}。` p = p + `\n\n最新的对话:\n[${time} ${message.talker().name()}]:${newText}\n[${time} ${keyWord}]:` log.info('提示词:', p) diff --git a/src/app/qa.ts b/src/app/qa.ts index d312d7a6..def72e04 100644 --- a/src/app/qa.ts +++ b/src/app/qa.ts @@ -12,33 +12,36 @@ import { async function handleAutoQAForContact (message: Message, keyWord: string) { const talker = message.talker() const text = message.text() - log.info('联系人请求智能问答:', (text === keyWord)) - - if (ChatFlowConfig.configEnv.AUTOQA_AUTOREPLY) { + const includesKeyWord = text.indexOf(keyWord) !== -1 + log.info('消息中包含关键字:', includesKeyWord) + const AUTOQA_AUTOREPLY = ChatFlowConfig.configEnv.AUTOQA_AUTOREPLY + log.info('自动问答开关开启:', AUTOQA_AUTOREPLY || false) + // 问答开关开启,且消息中包含关键字 + if (AUTOQA_AUTOREPLY && includesKeyWord) { // 判断是否在微信对话平台白名单内 const isInContactWhiteList = await containsContact(ChatFlowConfig.whiteList.contactWhiteList.qa, talker) if (isInContactWhiteList) { - log.info('当前好友在qa白名单内,请求问答...') + log.info('当前好友在【白名单|WhiteList/智能问答|qa】内,请求问答...') try { await wxai(ChatFlowConfig.configEnv, ChatFlowConfig.bot, message) } catch (e) { - log.error('当前好友在qa白名单内,发起请求wxai失败', e) + log.error('当前好友在【白名单|WhiteList/智能问答|qa】内,发起请求wxai失败', e) } } else { - log.info('当前好友不在qa白名单内,流程结束') + log.info('当前好友不在【白名单|WhiteList/智能问答|qa】内,流程结束') } // 判断是否在gpt白名单内 const isInGptContactWhiteList = await containsContact(ChatFlowConfig.whiteList.contactWhiteList.gpt, talker) if (isInGptContactWhiteList) { - log.info('当前好友在qa白名单内,请求问答gpt...') + log.info('当前好友在【白名单|WhiteList/ChatGPT|gpt】内,请求问答gpt...') try { await gpt(ChatFlowConfig.bot, message) } catch (e) { - log.error('发起请求gpt失败', e) + log.error('当前好友不在【白名单|WhiteList/ChatGPT|gpt】内,发起请求gpt失败', e) } } else { - log.info('当前好友不在gpt白名单内,gpt流程结束') + log.info('当前好友不在【白名单|WhiteList/ChatGPT|gpt】内,gpt流程结束') } } } @@ -48,29 +51,36 @@ async function handleAutoQA (message: Message, keyWord: string) { const room = message.room() as Room const topic = await room.topic() const text = message.text() - log.info('群消息请求智能问答:' + JSON.stringify(text === keyWord)) - if (ChatFlowConfig.configEnv.AUTOQA_AUTOREPLY) { + const includesKeyWord = text.indexOf(keyWord) !== -1 + log.info('消息中包含关键字:', includesKeyWord) + const AUTOQA_AUTOREPLY = ChatFlowConfig.configEnv.AUTOQA_AUTOREPLY + log.info('自动问答开关开启:', AUTOQA_AUTOREPLY || false) + + // 问答开关开启,且消息中包含关键字 + if (AUTOQA_AUTOREPLY && includesKeyWord) { // 判断是否在微信对话平台白名单内 const isInRoomWhiteList = await containsRoom(ChatFlowConfig.whiteList.roomWhiteList.qa, room) if (isInRoomWhiteList) { - log.info('当前群在qa白名单内,请求问答...') + log.info('当前群在【白名单|WhiteList/智能问答|qa】内,请求问答...') try { await wxai(ChatFlowConfig.configEnv, ChatFlowConfig.bot, message) } catch (e) { - log.error('当前群在qa白名单内,发起请求wxai失败', e) + log.error('当前群在【白名单|WhiteList/智能问答|qa】内,发起请求wxai失败', e) } } // 判断是否在gpt白名单内 const isInGptRoomWhiteList = await containsRoom(ChatFlowConfig.whiteList.roomWhiteList.gpt, room) if (isInGptRoomWhiteList) { - log.info('当前群在qa白名单内,请求问答gpt...') + log.info('当前群在【白名单|WhiteList/ChatGPT|gpt】白名单内,请求问答gpt...') try { await gpt(ChatFlowConfig.bot, message) } catch (e) { - log.error('发起请求gpt失败', topic, e) + log.error('当前群在【白名单|WhiteList/ChatGPT|gpt】白名单内,发起请求gpt失败', topic, e) } + } else { + log.info('当前群不在【白名单|WhiteList/ChatGPT|gpt】内,gpt流程结束') } } } From 57dd13880b7029e6e752ba281ba7fd85f3eb1838 Mon Sep 17 00:00:00 2001 From: atorber Date: Tue, 19 Dec 2023 20:16:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/message.ts | 4 +++- src/services/larkService.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/api/message.ts b/src/api/message.ts index aaa6b101..c8fbfbed 100644 --- a/src/api/message.ts +++ b/src/api/message.ts @@ -88,7 +88,7 @@ export interface MessageToCloud { } export const formatMessageToCloud = async (message: Message) => { - log.info('消息存储到lark...') + log.info('formatMessageToCloud 消息转换为存储到多维表格的格式...') const room = message.room() const talker = message.talker() try { @@ -328,8 +328,10 @@ export const saveMessageToCloud = async (record:any) => { // log.info('saveMessageToCloud messageNew:', JSON.stringify(messageNew)) try { if (ChatFlowConfig.dataBaseType === 'lark') { + log.info('消息写入到lark:', JSON.stringify(record)) await LarkChat.addChatRecord(record) } else { + log.info('消息写入到vika:', JSON.stringify(record)) await MessageChat.addChatRecord(record) } // log.info('消息写入数据库成功:', res._id) diff --git a/src/services/larkService.ts b/src/services/larkService.ts index 7c99db28..491b401d 100644 --- a/src/services/larkService.ts +++ b/src/services/larkService.ts @@ -215,7 +215,7 @@ export class LarkChat { } static async onMessage (message: Message) { - log.info('消息存储到lark...') + log.info('调用onMessage消息存储到lark...') const room = message.room() const talker = message.talker() const files: any = [] From 200398171dcb59328464689acb0cd56f6d8f8ca4 Mon Sep 17 00:00:00 2001 From: atorber Date: Fri, 22 Dec 2023 12:10:48 +0800 Subject: [PATCH 3/3] =?UTF-8?q?2.0.70=E4=BF=AE=E5=A4=8D=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E9=94=99=E8=AF=AFbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/handlers/on-message.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7d50f365..98a7881e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@atorber/chatflow", - "version": "2.0.69", + "version": "2.0.70", "description": "ChatFlow-聊天机器人管理平台", "type": "module", "exports": { diff --git a/src/handlers/on-message.ts b/src/handlers/on-message.ts index c396c85e..e784fd90 100644 --- a/src/handlers/on-message.ts +++ b/src/handlers/on-message.ts @@ -21,7 +21,7 @@ import { qa } from '../app/qa.js' import { handleActivityManagement } from '../app/activity.js' import { extractAtContent } from '../app/extract-at.js' -export async function onMessage (message: Message) { +export async function onMessage(message: Message) { // 存储消息到db,如果写入失败则终止,用于检测是否是重复消息 try { @@ -33,8 +33,12 @@ export async function onMessage (message: Message) { } // 输出格式化消息log - const chatMessage = await formatMessageToLog(message) - logForm(JSON.stringify(chatMessage)) + try { + const chatMessage = await formatMessageToLog(message) + logForm(JSON.stringify(chatMessage)) + } catch (e) { + log.error('消息格式化失败:\n', e) + } // 请求管理员群操作 try {