diff --git a/.env.example b/.env.example index 34f19392..eaf9133e 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ ADMINROOM_ADMINROOMTOPIC="替换为你的管理员群名称" # 管理群名称 VIKA_SPACE_ID="替换为你的维格表空间ID" # 维格表空间ID或飞书多维表格的appToken VIKA_TOKEN="替换为你的维格表token" # 维格表token或飞书多维表格信息拼接(使用'/'拼接三个参数:appId/appSecret/appToken) ENDPOINT="http://127.0.0.1:9503" # 后端管理服务API地址,默认http://127.0.0.1:9503 - +# ENDPOINT="http://120.48.99.192:9503" # 官方体验环境地址,可以直接使用,不需要启动chatflow-admin,但服务器不定时重启,不保证稳定 # --------------------------------分割线-------------------------------- # 以下配置无需关注,功能实现中,暂未使用,仅需要配置分割线以上的配置即可 diff --git a/README.md b/README.md index a91ca8d1..c7f3b2b8 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,28 @@ ChatFlow是一个聊天机器人管理系统,可以帮助你实现一些原生 > 升级代码后建议配置全新的维格表空间或删除原空间全部表,请使用nodejs16或18,最新的nodejs20可能无法运行 -1.下载源码并安装依赖 +最新部署方法参考:[ChatFlow3.0Beta部署运行](https://www.yuque.com/atorber/chatflow/gbpvgf01cw0nlxu4) + +1.下载代码及安装启动 + +1.1 下载并运行chatflow-admin ```Shell -git clone -cd ./chatflow -npm install +git clone https://github.com/atorber/chatflow-admin.git +cd chatflow-admin + +# 安装依赖 +npm i + +# 启动api服务 +npm run start:dev +``` + +1.2 下载并运行chatflow + +```Shell +git clone https://github.com/atorber/chatflow.git +cd chatflow ``` 2.分别登陆[微信对话开放平台](https://openai.weixin.qq.com/)和[vika维格表](https://spcp52tvpjhxm.com.vika.cn/?inviteCode=55152973)官网注册账号并获取token @@ -53,12 +69,17 @@ npm install > 快速开始仅需要修改VIKA_TOKEN、VIKA_SPACE_NAME、ADMINROOM_ADMINROOMTOPIC配置项,其他配置项暂时无需修改,使用微信对话开放平台时配置WXOPENAI_TOKEN、WXOPENAI_ENCODINGAESKEY ```.env -# 维格表配置 -VIKA_SPACE_ID="替换为自己的维格表空间ID" -VIKA_TOKEN="替换为自己的维格表token" +# Wechaty +WECHATY_PUPPET="wechaty-puppet-wechat4u" # 可选值:wechaty-puppet-wechat4u、wechaty-puppet-wechat、wechaty-puppet-xp、wechaty-puppet-engine、wechaty-puppet-padlocal、wechaty-puppet-service +WECHATY_TOKEN="" # 使用wechaty-puppet-padlocal、wechaty-puppet-service时需配置此token # 基础配置 -ADMINROOM_ADMINROOMTOPIC="瓦力是群主" # 管理群名称,需尽量保持名称复杂,避免重名群干扰 +ADMINROOM_ADMINROOMTOPIC="替换为你的管理员群名称" # 管理群名称,需尽量保持名称复杂,避免重名群干扰 + +# 维格表配置 +VIKA_SPACE_ID="替换为你的维格表空间ID" # 维格表空间ID或飞书多维表格的appToken +VIKA_TOKEN="替换为你的维格表token" # 维格表token或飞书多维表格信息拼接(使用'/'拼接三个参数:appId/appSecret/appToken) +ENDPOINT="http://127.0.0.1:9503" # 后端管理服务API地址,默认http://127.0.0.1:9503 ``` 4.启动程序 @@ -172,6 +193,10 @@ atorber/chatflow:latest ## 更新日志 +### 3.0.0-Beta-11 + +- 移除环境变量依赖 + ### 3.0.0-Beta-10 - 新增媒体资源接口 diff --git a/example/ding-dong-bot.ts b/example/ding-dong-bot.ts index 7f64b951..d72d2841 100644 --- a/example/ding-dong-bot.ts +++ b/example/ding-dong-bot.ts @@ -9,9 +9,6 @@ import { ChatFlow, getBotOps, logForm, - // LarkDB, - GroupMaster, - GroupMasterConfig, init, } from '../src/index.js' @@ -38,27 +35,6 @@ const main = async () => { logForm('初始化检查失败:' + JSON.stringify(e)) } - // 使用Lark - // await LarkDB.init({ - // appId: process.env['LARK_APP_ID'], - // appSecret: process.env['LARK_APP_SECRET'], - // appToken: process.env['LARK_BITABLE_APP_TOKEN'], - // userMobile: process.env['LARK_APP_USER_MOBILE'], - // }) - - // 如果配置了群管理秘书,则启动群管理秘书,这是一个探索性功能,暂未开放,可以忽略 - if (process.env['GROUP_MASTER_ENDPOINT']) { - const configGroupMaster: GroupMasterConfig = { - WX_KEY:process.env['GROUP_MASTER_WX_KEY'] || '', - MQTT_ENDPOINT:process.env['GROUP_MASTER_MQTT_ENDPOINT'] || '', - MQTT_USERNAME:process.env['GROUP_MASTER_MQTT_USERNAME'] || '', - MQTT_PASSWORD:process.env['GROUP_MASTER_MQTT_PASSWORD'] || '', - MQTT_PORT:Number(process.env['GROUP_MASTER_MQTT_PORT'] || '1883'), - HOST:process.env['GROUP_MASTER_ENDPOINT'] || '', - } - bot.use(GroupMaster(configGroupMaster)) - } - // 启用ChatFlow插件 bot.use(ChatFlow({ spaceId: VIKA_SPACE_ID, diff --git a/example/group-master-worker.ts b/example/group-master-worker.ts index 30ad49d6..a118ae11 100644 --- a/example/group-master-worker.ts +++ b/example/group-master-worker.ts @@ -11,14 +11,20 @@ const main = async () => { const puppet = process.env['WECHATY_PUPPET'] const token = process.env['WECHATY_TOKEN'] + const WX_KEY = process.env['GROUP_MASTER_WX_KEY'] || '' + const MQTT_ENDPOINT = process.env['GROUP_MASTER_MQTT_ENDPOINT'] || '' + const MQTT_USERNAME = process.env['GROUP_MASTER_MQTT_USERNAME'] || '' + const MQTT_PASSWORD = process.env['GROUP_MASTER_MQTT_PASSWORD'] || '' + const MQTT_PORT = Number(process.env['GROUP_MASTER_MQTT_PORT'] || '1883') + const HOST = process.env['GROUP_MASTER_ENDPOINT'] || '' const config: GroupMasterConfig = { - WX_KEY:process.env['GROUP_MASTER_WX_KEY'] || '', - MQTT_ENDPOINT:process.env['GROUP_MASTER_MQTT_ENDPOINT'] || '', - MQTT_USERNAME:process.env['GROUP_MASTER_MQTT_USERNAME'] || '', - MQTT_PASSWORD:process.env['GROUP_MASTER_MQTT_PASSWORD'] || '', - MQTT_PORT:Number(process.env['GROUP_MASTER_MQTT_PORT'] || '1883'), - HOST:process.env['GROUP_MASTER_ENDPOINT'] || '', + WX_KEY, + MQTT_ENDPOINT, + MQTT_USERNAME, + MQTT_PASSWORD, + MQTT_PORT, + HOST, } // 构建机器人 const ops = getBotOps(puppet, token) diff --git a/example/init-db.ts b/example/init-db.ts deleted file mode 100644 index ae501278..00000000 --- a/example/init-db.ts +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env -S node --no-warnings --loader ts-node/esm -import 'dotenv/config.js' - -import { - logForm, - init, -} from '../src/index.js' - -const main = async () => { - - // 从环境变量中获取配置信息, 在环境变量中已经配置了以下信息或者直接赋值 - const VIKA_SPACE_ID = process.env['VIKA_SPACE_ID'] - const VIKA_TOKEN = process.env['VIKA_TOKEN'] - - // 初始化检查数据库表,如果不存在则创建 - try { - await init({ - spaceId: VIKA_SPACE_ID, - token: VIKA_TOKEN, - }) - } catch (e) { - logForm('初始化检查失败:' + JSON.stringify(e)) - } -} - -void main() diff --git a/package.json b/package.json index dab304fa..6796c504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@atorber/chatflow", - "version": "3.0.0-Beta-10", + "version": "3.0.0-Beta-11", "description": "ChatFlow-聊天机器人管理平台", "type": "module", "exports": { @@ -108,7 +108,8 @@ "typescript": "^4.9.3", "wechaty": "^1.20.2", "wechaty-puppet-padlocal": "^1.20.1", - "wechaty-puppet-wechat4u": "^1.14.12" + "wechaty-puppet-wechat4u": "^1.14.12", + "wechaty-puppet-xp": "^1.13.8" }, "files": [ "bin/", diff --git a/src/api/admin.ts b/src/api/admin.ts index 8499f9f3..9afdaf70 100644 --- a/src/api/admin.ts +++ b/src/api/admin.ts @@ -276,6 +276,48 @@ export const adminAction = async (message:Message) => { const tableName = ChatFlowConfig.db.dataBaseNames[tableCode as keyof typeof ChatFlowConfig.db.dataBaseNames] log.info('数据表名称:', tableName) await message.say(`检测到多维表格链接,表格名称:【${tableName}】,\n是否需要处理?\n配置信息:\n${JSON.stringify(config, null, 2)}`) + + switch (tableCode) { + case 'mediaSheet': + await ChatFlowConfig.updateMediaList() + break + case 'envSheet': + await ChatFlowConfig.updateEnv() + break + case 'groupSheet': + await ChatFlowConfig.updateGroup() + break + case 'chatBotUserSheet': + await ChatFlowConfig.updateChatBotUser() + break + case 'chatBotSheet': + await ChatFlowConfig.updateChatBot() + break + case 'whiteListSheet': + await ChatFlowConfig.updateWhiteList() + break + case 'groupNoticeSheet': + await ChatFlowConfig.updateGroupNotifications() + break + case 'statisticSheet': + await ChatFlowConfig.updateStatistics() + break + case 'noticeSheet': + await ChatFlowConfig.updateReminder() + break + case 'qaSheet': + await ChatFlowConfig.updateQaList() + break + case 'keywordSheet': + await ChatFlowConfig.updateKeywords() + break + case 'welcomeSheet': + await ChatFlowConfig.updateWelcomes() + break + default: + log.info('多维表格配置信息不需要处理...') + } + } else { log.info('多维表格配置信息不匹配,不处理...') await message.say(`检测到多维表格链接,但不是当前系统的多维表格链接,不处理~\n配置信息:\n${JSON.stringify(config, null, 2)}`) diff --git a/src/api/base-config.ts b/src/api/base-config.ts index 84bdc50b..e20d6f71 100644 --- a/src/api/base-config.ts +++ b/src/api/base-config.ts @@ -13,13 +13,17 @@ import type { IClientOptions, } from '../proxy/mqtt-proxy.js' import CryptoJS from 'crypto-js' -import { ServeGetUserConfigObj } from '../api/user.js' +import { + ServeGetUserConfigObj, + // ServeGetUserConfig, +} from '../api/user.js' import { delay, logger } from '../utils/utils.js' import { ServeGetContactsRaw, ServeDeleteBatchContact, ServeCreateContactBatch, + ServeContactGroupList, } from '../api/contact.js' import { @@ -29,6 +33,17 @@ import { } from '../api/room.js' import { ServeGetKeywords } from '../api/keyword.js' +import { ServeGetMedias } from '../api/media.js' +import { ServeGetWhitelistWhiteObject } from '../api/white-list.js' +import { ServeGetGroupnotices } from '../api/group-notice.js' +import { ServeGetStatistics } from '../api/statistic.js' +import { ServeGetNotices } from '../api/notice.js' +import { + ServeGetChatbots, + ServeGetChatbotUsers, +} from '../api/chatbot.js' +import { ServeGetWelcomes } from '../api/welcome.js' +import { ServeGetQas } from '../api/qa.js' export interface ChatBotUser { id: string; @@ -41,7 +56,7 @@ export interface ChatBotUser { info: string; recordId: string; contact?: BusinessUser; - room?:BusinessRoom; + room?: BusinessRoom; chatbot: ChatBot; } @@ -99,7 +114,7 @@ export interface DateBase { orderSheet: string stockSheet: string groupNoticeSheet: string - qaSheet:string + qaSheet: string chatBotSheet: string chatBotUserSheet: string groupSheet: string @@ -113,7 +128,7 @@ export class BiDirectionalMap { private reverseMap: Map constructor (fields: any[]) { - const initPairs:[string, string][] = fields.map((fields:any) => { + const initPairs: [string, string][] = fields.map((fields: any) => { return this.transformKey(fields.name) }) this.reverseMap = new Map(initPairs) @@ -141,13 +156,14 @@ export class ChatFlowConfig { static switchsOnVika: any[] static reminderList: any[] static statisticRecords: any - static configEnv : ProcessEnv + static configEnv: ProcessEnv static spaceId: string static token: string = '' static adminRoomTopic?: string static endpoint?: string - static bot:Wechaty - static db:{ + static bot: Wechaty + static adminRoom: Room | undefined + static db: { dataBaseIds: DateBase, dataBaseNames: DateBase, dataBaseIdsMap: { @@ -157,7 +173,7 @@ export class ChatFlowConfig { token: string, } - static whiteList:WhiteList = { + static whiteList: WhiteList = { contactWhiteList: { qa: [], msg: [], @@ -195,12 +211,24 @@ export class ChatFlowConfig { state: string, }[] = [] - static async init (options:{ - spaceId?:string, - token?:string, + static setOptions (options: { + spaceId: string, + token: string, + endpoint?: string, + }) { + ChatFlowConfig.spaceId = options.spaceId || ChatFlowConfig.spaceId + ChatFlowConfig.token = options.token || ChatFlowConfig.token + ChatFlowConfig.endpoint = options.endpoint || ChatFlowConfig.token || 'http://127.0.0.1:9503' + } + + static async init (options: { + spaceId?: string, + token?: string, + endpoint?: string, }) { ChatFlowConfig.spaceId = options.spaceId || ChatFlowConfig.spaceId ChatFlowConfig.token = options.token || ChatFlowConfig.token + ChatFlowConfig.endpoint = options.endpoint || ChatFlowConfig.token || 'http://127.0.0.1:9503' // log.info('初始化维格配置信息...,init()') const userConfig = await ServeGetUserConfigObj() // log.info('userConfig', JSON.stringify(userConfig)) @@ -215,11 +243,11 @@ export class ChatFlowConfig { // clientid加密 const client = CryptoJS.SHA256(clientString).toString() - const mqttConfig:IClientOptions = { + const mqttConfig: IClientOptions = { username: this.configEnv.MQTT_USERNAME, password: this.configEnv.MQTT_PASSWORD, host: this.configEnv.MQTT_ENDPOINT, - protocol:'mqtts', + protocol: 'mqtts', port: Number(this.configEnv.MQTT_PORT), clientId: client, clean: false, @@ -246,7 +274,7 @@ export class ChatFlowConfig { } // 上传联系人列表 - static async updateContacts (puppet:string) { + static async updateContacts (puppet: string) { let updateCount = 0 try { const contacts: Contact[] = await this.bot.Contact.findAll() @@ -263,7 +291,7 @@ export class ChatFlowConfig { let wxids: string[] = [] const recordIds: string[] = [] if (recordExisting.length) { - recordExisting.forEach((fields:any) => { + recordExisting.forEach((fields: any) => { wxids.push(fields['id'] as string) recordIds.push(fields.recordId) }) @@ -281,7 +309,7 @@ export class ChatFlowConfig { for (let i = 0; i < count; i++) { const records = recordIds.splice(0, batchCount) log.info('删除:', records.length) - await ServeDeleteBatchContact({ recordIds:records }) + await ServeDeleteBatchContact({ recordIds: records }) await delay(delayTime) } wxids = [] @@ -297,7 +325,7 @@ export class ChatFlowConfig { // if(item) log.info('头像信息:', (JSON.stringify((await item?.avatar()).toJSON()))) if (item && isFriend && !wxids.includes(item.id)) { // logger.info('云端不存在:' + item.name()) - let avatar:any = '' + let avatar: any = '' let alias = '' try { avatar = (await item.avatar()).toJSON() @@ -342,7 +370,7 @@ export class ChatFlowConfig { } // 上传群列表 - static async updateRooms (puppet:string) { + static async updateRooms (puppet: string) { let updateCount = 0 // 根据多维表格类型设置批量操作的数量和延迟时间 const batchCount = ChatFlowConfig.token.indexOf('/') === -1 ? 10 : 100 @@ -374,7 +402,7 @@ export class ChatFlowConfig { for (let i = 0; i < count; i++) { const records = recordIds.splice(0, batchCount) logger.info('删除:', records.length) - await ServeDeleteBatchGroups({ recordIds:records }) + await ServeDeleteBatchGroups({ recordIds: records }) await delay(delayTime) } wxids = [] @@ -434,7 +462,7 @@ export class ChatFlowConfig { const res = await ServeGetKeywords() const records = res.data.items - let text :string = '【操作说明】\n' + let text: string = '【操作说明】\n' for (const fields of records) { text += `${fields['name']}:${fields['desc']}\n` } @@ -447,11 +475,95 @@ export class ChatFlowConfig { const res = await ServeGetKeywords() const records = res.data.items ChatFlowConfig.keywordList = records - let text :string = '【操作说明】\n' + let text: string = '【操作说明】\n' for (const fields of records) { if (fields['type'] === '系统指令') text += `${fields['name']} : ${fields['desc']}\n` } return text } + // 更新媒体资源库 + static async updateMediaList () { + const res = await ServeGetMedias({}) + const mediaList = res.data.items + log.info('获取的媒体资源库:' + JSON.stringify(mediaList)) + } + + // 更新环境变量 + static async updateEnv () { + const res = await ServeGetUserConfigObj() + const env = res.data + log.info('获取的环境变量:' + JSON.stringify(env)) + } + + // 更新分组 + static async updateGroup () { + const res = await ServeContactGroupList() + const groups = res.data.items + log.info('获取的分组:' + JSON.stringify(groups)) + } + + // 更新智聊用户名单 + static async updateChatBotUser () { + const res = await ServeGetChatbotUsers() + const chatBotUsers = res.data + log.info('获取的智聊用户名单:' + JSON.stringify(chatBotUsers)) + } + + // 更新智聊ChatBot + static async updateChatBot () { + const res = await ServeGetChatbots() + const chatBots = res.data + log.info('获取的智聊ChatBot:' + JSON.stringify(chatBots)) + } + + // 更新白名单 + static async updateWhiteList () { + const res = await ServeGetWhitelistWhiteObject() + const whiteList = res.data + log.info('获取的白名单:' + JSON.stringify(whiteList)) + } + + // 更新群发通知 + static async updateGroupNotifications () { + const res = await ServeGetGroupnotices() + const groupNotifications = res.data + log.info('获取的群发通知:' + JSON.stringify(groupNotifications)) + } + + // 更新统计打卡 + static async updateStatistics () { + const res = await ServeGetStatistics() + const statistics = res.data.items + log.info('获取的统计打卡:' + JSON.stringify(statistics)) + } + + // 更新定时提醒 + static async updateReminder () { + const res = await ServeGetNotices() + const reminders = res.data.items + log.info('获取的定时提醒:' + JSON.stringify(reminders)) + } + + // 更新问答列表 + static async updateQaList () { + const res = await ServeGetQas() + const qaList = res.data.items + log.info('获取的问答列表:' + JSON.stringify(qaList)) + } + + // 更新关键字 + static async updateKeywords () { + const res = await ServeGetKeywords() + const keywords = res.data.items + log.info('获取的关键字:' + JSON.stringify(keywords)) + } + + // 更新进群欢迎语 + static async updateWelcomes () { + const res = await ServeGetWelcomes() + const welcomes = res.data.items + log.info('获取的进群欢迎语:' + JSON.stringify(welcomes)) + } + } diff --git a/src/api/contact.ts b/src/api/contact.ts index ef68aa8c..23db0e56 100644 --- a/src/api/contact.ts +++ b/src/api/contact.ts @@ -72,7 +72,7 @@ export const ServeSearchUser = (data: {} | undefined) => { } // 搜索用户信息服务接口 -export const ServeContactGroupList = (data: {} | undefined) => { +export const ServeContactGroupList = (data?: any) => { return get('/api/v1/contact/group/list', data) } diff --git a/src/api/message.ts b/src/api/message.ts index c2edcbf0..87c83500 100644 --- a/src/api/message.ts +++ b/src/api/message.ts @@ -85,7 +85,7 @@ export interface MessageToCloud { } export const formatMessageToCloud = async (message: Message) => { - log.info('formatMessageToCloud 消息转换为存储到多维表格的格式...') + // log.info('formatMessageToCloud 消息转换为存储到多维表格的格式...') const room = message.room() const talker = message.talker() try { diff --git a/src/app/chatbot.ts b/src/app/chatbot.ts index c1fe6252..a5ffca6d 100644 --- a/src/app/chatbot.ts +++ b/src/app/chatbot.ts @@ -47,7 +47,7 @@ async function chatbot (message: Message) { logger.info('智聊服务chatBotUser:' + JSON.stringify(chatBotUser)) // log.info('当前用户或群:' + JSON.stringify(room) + JSON.stringify(talker)) - log.info('智聊服务chatBotUser:' + JSON.stringify(chatBotUser)) + // log.info('智聊服务chatBotUser:' + JSON.stringify(chatBotUser)) try { if (message.type() === types.Message.Text && chatBotUser !== undefined) { diff --git a/src/handlers/on-message.ts b/src/handlers/on-message.ts index d4bf908b..4d5d063d 100644 --- a/src/handlers/on-message.ts +++ b/src/handlers/on-message.ts @@ -28,6 +28,9 @@ import { getFormattedRideInfo } from '../plugins/mod.js' export async function onMessage (message: Message) { const text = message.text() + const isSelf = message.self() + const talker = message.talker() + const room = message.room() // 存储消息到db,如果写入失败则终止,用于检测是否是重复消息 try { const messageToDB = await formatMessageToDB(message) @@ -45,7 +48,7 @@ export async function onMessage (message: Message) { log.error('消息格式化失败:\n', e) } - if (ChatFlowConfig.isReady) { + if (ChatFlowConfig.isReady && !isSelf) { // 请求管理员群操作 try { await adminAction(message) @@ -71,9 +74,10 @@ export async function onMessage (message: Message) { // 关键字回复 try { if (text && ChatFlowConfig.keywordList.length > 0) { - log.info('触发关键字回复...') + log.info('检测关键字回复...') const keywordItem = ChatFlowConfig.keywordList.find((item) => item.name === text && item.type === '等于关键字') if (keywordItem) { + log.info('触发关键字回复,关键字是:', keywordItem.name) await message.say(keywordItem.desc) } } @@ -115,9 +119,7 @@ export async function onMessage (message: Message) { } // 群消息处理,判断非机器人自己发的消息 - const room = message.room() - const isSelf = message.self() - if (room && room.id && !isSelf) { + if (room && room.id) { try { // 活动管理 await handleActivityManagement(message, room) @@ -132,8 +134,8 @@ export async function onMessage (message: Message) { log.error('提取@消息失败 error:', e) } - // 检测顺风车信息,如果text中包含车找人或人找车关键字,则提取信息并回复 - if (text.includes('车找人') || text.includes('人找车')) { + // 检测顺风车信息,如果text中包含车找人、人找车、车找n人、n人找车(其中n是一个数字)关键字,则提取信息并回复 + if (text.includes('车找') || text.includes('找车')) { try { const rideInfo = await getFormattedRideInfo(message) // log.info('rideInfo信息:', JSON.stringify(rideInfo, null, 2)) @@ -144,6 +146,38 @@ export async function onMessage (message: Message) { log.error('检测顺风车信息失败 error:', e) } } + + // 检测text中是否存在微信ID以及回复内容,提取出nickName、wxid和text,例如text="瓦力:[luyuchao@ledongmao]\n 你在干嘛」\n- - - - - - - - - - - - - - -\n好的,我知道了",则提取出luyuchao、ledongmao和你在干嘛 + try { + const atContent = text.match(/\[(.+)@(.+)\]\n(.+)」\n(.+)\n(.+)/) + log.info('atContent:', atContent) + if (atContent) { + const nickName = atContent[1] + const wxid = atContent[2] + log.info('nickName:', nickName, 'wxid:', wxid) + + // 如果wxid不是weixin或者gh_开头的公众号ID,则提取出回复内容并回复 + if (wxid) { + const contact = await ChatFlowConfig.bot.Contact.find({ id: wxid }) + const content = text.split('- - -\n')[1] + if (contact && content) { + await contact.say(content) + } + } + } else { + log.info('没有找到微信ID') + } + } catch (e) { + log.error('提取微信ID失败 error:', e) + } + + } else { + // 转发到adminRoom + const wxid = talker.id + if (ChatFlowConfig.adminRoom && ![ 'weixin' ].includes(wxid) && wxid.includes('gh_') === false) { + const adminRoom = ChatFlowConfig.adminRoom + await adminRoom.say(`[${talker.name()}@${talker.id}]\n ${message.text()}`) + } } // 消息存储到云表格,维格表或者飞书多维表格 diff --git a/src/handlers/onReadyOrLogin.ts b/src/handlers/onReadyOrLogin.ts index 66ec312b..32216ac0 100644 --- a/src/handlers/onReadyOrLogin.ts +++ b/src/handlers/onReadyOrLogin.ts @@ -39,10 +39,12 @@ const notifyAdminRoom = async (bot: Wechaty) => { if (ChatFlowConfig.configEnv.ADMINROOM_ADMINROOMID || ChatFlowConfig.configEnv.ADMINROOM_ADMINROOMTOPIC) { const adminRoom = await getRoom(bot, { topic: ChatFlowConfig.configEnv.ADMINROOM_ADMINROOMTOPIC, id: ChatFlowConfig.configEnv.ADMINROOM_ADMINROOMID }) - const helpText = await ChatFlowConfig.getSystemKeywordsText() const text = `${new Date().toLocaleString()}\nchatflow启动成功!\n当前登录用户${bot.currentUser.name()}\n可在管理员群回复对应指令进行操作\n${helpText}\n` - if (adminRoom) await handleSay(adminRoom, text) + if (adminRoom) { + ChatFlowConfig.adminRoom = adminRoom + await handleSay(adminRoom, text) + } // await adminRoom?.say(text) } } @@ -188,7 +190,7 @@ export const onReadyOrLogin = async (bot: Wechaty) => { } const ADMINROOM_ADMINROOMTOPIC = baseConfig.find((item) => item.key === 'ADMINROOM_ADMINROOMTOPIC') - const topic = ChatFlowConfig.adminRoomTopic || process.env.ADMINROOM_ADMINROOMTOPIC || '' + const topic = ChatFlowConfig.adminRoomTopic || '' if (ADMINROOM_ADMINROOMTOPIC && topic) { ADMINROOM_ADMINROOMTOPIC.name = `基础配置-${ADMINROOM_ADMINROOMTOPIC.name}` ADMINROOM_ADMINROOMTOPIC.value = topic @@ -209,6 +211,33 @@ export const onReadyOrLogin = async (bot: Wechaty) => { } } + const wechatyConfig:{ + id?:string; + name:string; + value:any; + key:string; + lastOperationTime:number; + syncStatus:string; + }[] = configGgroup['Wechaty'] + + const WECHATY_PUPPET = wechatyConfig.find((item) => item.key === 'WECHATY_PUPPET') + if (WECHATY_PUPPET) { + WECHATY_PUPPET.name = `Wechaty-${WECHATY_PUPPET.name}` + WECHATY_PUPPET.value = bot.puppet.name() + WECHATY_PUPPET.lastOperationTime = curTime + WECHATY_PUPPET.syncStatus = '已同步' + baseInfo.push(WECHATY_PUPPET) + } + + const WECHATY_TOKEN = wechatyConfig.find((item) => item.key === 'WECHATY_TOKEN') + if (WECHATY_TOKEN) { + WECHATY_TOKEN.name = `Wechaty-${WECHATY_TOKEN.name}` + WECHATY_TOKEN.value = '' + WECHATY_TOKEN.lastOperationTime = curTime + WECHATY_TOKEN.syncStatus = '已同步' + baseInfo.push(WECHATY_TOKEN) + } + if (baseInfo.length > 0) { baseInfo = baseInfo.map((item) => { const raw:any = { diff --git a/src/index.ts b/src/index.ts index 59fff54c..a0368d12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,10 +29,10 @@ function ChatFlow (options?:{ return function ChatFlowPlugin (bot: Wechaty): void { ChatFlowConfig.bot = bot - ChatFlowConfig.spaceId = options?.spaceId || process.env.VIKA_SPACE_ID || '' - ChatFlowConfig.token = options?.token || process.env.VIKA_TOKEN || '' - ChatFlowConfig.adminRoomTopic = options?.adminRoomTopic || process.env.ADMINROOM_ADMINROOMTOPIC || '' - ChatFlowConfig.endpoint = options?.endpoint || process.env.ENDPOINT || '' + ChatFlowConfig.spaceId = options?.spaceId || '' + ChatFlowConfig.token = options?.token || '' + ChatFlowConfig.adminRoomTopic = options?.adminRoomTopic || '' + ChatFlowConfig.endpoint = options?.endpoint || '' bot.on('scan', onScan) bot.on('login', onLogin) @@ -51,10 +51,14 @@ const init = async (options:{ token: string, endpoint?: string, }) => { - + ChatFlowConfig.setOptions(options) // 远程加载配置信息,初始化api客户端 try { - const authClient = getAuthClient() + const authClient = getAuthClient({ + password: options.token, + username: options.spaceId, + endpoint: options.endpoint || '', + }) try { // 初始化检查数据库表,如果不存在则创建 const initRes = await authClient.init(options.spaceId, options.token) diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 9650dc5a..e47153cf 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -21,17 +21,28 @@ class AuthClient { private tokenExpirationDate: Date | null = null private username: string = '' private password: string = '' - private endpoint: string = `${process.env.ENDPOINT || 'http://127.0.0.1:9503'}/api/v1` + endpoint: string = 'http://127.0.0.1:9503' - private constructor () { // 步骤2:私有构造函数 + private constructor (ops?:{ + username:string; + password:string, + endpoint:string + }) { // 步骤2:私有构造函数 + this.username = ops?.username || this.username + this.password = ops?.password || this.password + this.endpoint = ops?.endpoint ? ops.endpoint : 'http://127.0.0.1:9503' this.axiosInstance = axios.create({ baseURL: this.endpoint, // 你的 API 基础地址 }) } - public static getInstance (): AuthClient { // 步骤3:公共静态方法 + public static getInstance (ops?:{ + username:string; + password:string, + endpoint:string + }): AuthClient { // 步骤3:公共静态方法 if (!AuthClient.instance) { - AuthClient.instance = new AuthClient() + AuthClient.instance = new AuthClient(ops) } return AuthClient.instance } @@ -40,7 +51,7 @@ class AuthClient { this.username = username || this.username this.password = password || this.password try { - const response = await this.axiosInstance.post('/auth/login', { + const response = await this.axiosInstance.post('/api/v1/auth/login', { mobile: this.username, password: this.password, }) @@ -63,7 +74,7 @@ class AuthClient { this.username = username || this.username this.password = password || this.password try { - const response:{data:{message:string}} = await this.axiosInstance.post('/auth/init', { + const response:{data:{message:string}} = await this.axiosInstance.post('/api/v1/auth/init', { mobile: this.username, password: this.password, }) diff --git a/src/utils/request.ts b/src/utils/request.ts index c5a1d0ff..ba1d3658 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -12,7 +12,7 @@ const authClient = getAuthClient() // 创建 axios 实例 const request = axios.create({ // API 请求的默认前缀 - baseURL: process.env.ENDPOINT || 'http://127.0.0.1:9503', + baseURL: authClient.endpoint || 'http://127.0.0.1:9503', // 请求超时时间 timeout: 120000, diff --git a/tests/auth.ts b/tests/auth.ts index fd3cd2b6..cebaf19e 100644 --- a/tests/auth.ts +++ b/tests/auth.ts @@ -32,9 +32,9 @@ import 'dotenv/config.js' // 导入环境变量配置 // import { ServeGetMedias } from '../src/api/media.js' const authClient = getAuthClient() -await authClient.login(process.env.VIKA_SPACE_ID, process.env.VIKA_TOKEN) const res = await authClient.init(process.env.VIKA_SPACE_ID, process.env.VIKA_TOKEN) console.log('res:', JSON.stringify(res.data, null, 2)) +await authClient.login() // const task = await ServeGetNoticesTask() // console.log('task:', JSON.stringify(task, null, 2))