wechat-bot
Version:
255 lines (237 loc) • 7.88 kB
JavaScript
import { Command } from 'commander'
import { WechatyBuilder, ScanStatus, log } from 'wechaty'
import inquirer from 'inquirer'
import qrTerminal from 'qrcode-terminal'
import dotenv from 'dotenv'
import fs from 'fs'
import path, { dirname } from 'path'
import { fileURLToPath } from 'url'
import { defaultMessage } from './wechaty/sendMessage.js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const env = dotenv.config().parsed // 环境参数
const { version, name } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'))
// 扫码
function onScan(qrcode, status) {
if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
// 在控制台显示二维码
qrTerminal.generate(qrcode, { small: true })
const qrcodeImageUrl = ['https://api.qrserver.com/v1/create-qr-code/?data=', encodeURIComponent(qrcode)].join('')
console.log('onScan:', qrcodeImageUrl, ScanStatus[status], status)
} else {
log.info('onScan: %s(%s)', ScanStatus[status], status)
}
}
// 登录
function onLogin(user) {
console.log(`${user} has logged in`)
const date = new Date()
console.log(`Current time:${date}`)
console.log(`Automatic robot chat mode has been activated`)
}
// 登出
function onLogout(user) {
console.log(`${user} has logged out`)
}
// 收到好友请求
async function onFriendShip(friendship) {
const frienddShipRe = /chatgpt|chat/
if (friendship.type() === 2) {
if (frienddShipRe.test(friendship.hello())) {
await friendship.accept()
}
}
}
/**
* 消息发送
* @param msg
* @param isSharding
* @returns {Promise<void>}
*/
async function onMessage(msg) {
// 默认消息回复
await defaultMessage(msg, bot, serviceType)
// 消息分片
// await shardingMessage(msg,bot)
}
// 初始化机器人
const CHROME_BIN = process.env.CHROME_BIN ? { endpoint: process.env.CHROME_BIN } : {}
let serviceType = ''
export const bot = WechatyBuilder.build({
name: 'WechatEveryDay',
puppet: 'wechaty-puppet-wechat4u', // 如果有token,记得更换对应的puppet
// puppet: 'wechaty-puppet-wechat', // 如果 wechaty-puppet-wechat 存在问题,也可以尝试使用上面的 wechaty-puppet-wechat4u ,记得安装 wechaty-puppet-wechat4u
puppetOptions: {
uos: true,
...CHROME_BIN,
},
})
// 扫码
bot.on('scan', onScan)
// 登录
bot.on('login', onLogin)
// 登出
bot.on('logout', onLogout)
// 收到消息
bot.on('message', onMessage)
// 添加好友
bot.on('friendship', onFriendShip)
// 错误
bot.on('error', (e) => {
console.error('❌ bot error handle: ', e)
// console.log('❌ 程序退出,请重新运行程序')
// bot.stop()
// // 如果 WechatEveryDay.memory-card.json 文件存在,删除
// if (fs.existsSync('WechatEveryDay.memory-card.json')) {
// fs.unlinkSync('WechatEveryDay.memory-card.json')
// }
// process.exit()
})
// 启动微信机器人
function botStart() {
bot
.start()
.then(() => console.log('Start to log in wechat...'))
.catch((e) => console.error('❌ botStart error: ', e))
}
process.on('uncaughtException', (err) => {
if (err.code === 'ERR_ASSERTION') {
console.error('❌ uncaughtException 捕获到断言错误: ', err.message)
} else {
console.error('❌ uncaughtException 捕获到未处理的异常: ', err)
}
// if (fs.existsSync('WechatEveryDay.memory-card.json')) {
// fs.unlinkSync('WechatEveryDay.memory-card.json')
// }
})
// 控制启动
function handleStart(type) {
serviceType = type
console.log('🌸🌸🌸 / type: ', type)
switch (type) {
case 'ChatGPT':
if (env.OPENAI_API_KEY) return botStart()
console.log('❌ 请先配置.env文件中的 OPENAI_API_KEY')
break
case 'doubao':
if (env.DOUBAO_API_KEY) return botStart()
console.log('❌ 请先配置.env文件中的 DOUBAO_API_KEY')
break
case 'deepseek':
if (env.DEEPSEEK_API_KEY) return botStart()
console.log('❌ 请先配置.env文件中的 DEEPSEEK_API_KEY')
break
case 'Kimi':
if (env.KIMI_API_KEY) return botStart()
console.log('❌ 请先配置.env文件中的 KIMI_API_KEY')
break
case 'Xunfei':
if (env.XUNFEI_APP_ID && env.XUNFEI_API_KEY && env.XUNFEI_API_SECRET) {
return botStart()
}
console.log('❌ 请先配置.env文件中的 XUNFEI_APP_ID,XUNFEI_API_KEY,XUNFEI_API_SECRET')
break
case 'deepseek-free':
if (env.DEEPSEEK_FREE_URL && env.DEEPSEEK_FREE_TOKEN && env.DEEPSEEK_FREE_MODEL) {
return botStart()
}
console.log('❌ 请先配置.env文件中的 DEEPSEEK_FREE_URL,DEEPSEEK_FREE_TOKEN,DEEPSEEK_FREE_MODEL')
break
case '302AI':
if (env._302AI_API_KEY) {
return botStart()
}
console.log('❌ 请先配置.env文件中的 _302AI_API_KEY')
break
case 'dify':
if (env.DIFY_API_KEY && env.DIFY_URL) {
return botStart()
}
console.log('❌ 请先配置.env文件中的 DIFY_API_KEY')
break
case 'ollama':
if (env.OLLAMA_URL && env.OLLAMA_MODEL) {
return botStart()
}
break
case 'tongyi':
if (env.TONGYI_URL && env.TONGYI_MODEL) {
return botStart()
}
break
case 'claude':
if (env.CLAUDE_API_KEY && env.CLAUDE_MODEL) {
return botStart()
}
console.log('❌ 请先配置.env文件中的 CLAUDE_API_KEY 和 CLAUDE_MODEL')
break
default:
console.log('❌ 服务类型错误, 目前支持: ChatGPT | doubao | deepseek | Kimi | Xunfei | DIFY | OLLAMA | TONGYI')
}
}
export const serveList = [
{ name: 'ChatGPT', value: 'ChatGPT' },
{ name: 'doubao', value: 'doubao' },
{ name: 'deepseek', value: 'deepseek' },
{ name: 'Kimi', value: 'Kimi' },
{ name: 'Xunfei', value: 'Xunfei' },
{ name: 'deepseek-free', value: 'deepseek-free' },
{ name: '302AI', value: '302AI' },
{ name: 'dify', value: 'dify' },
// ... 欢迎大家接入更多的服务
{ name: 'ollama', value: 'ollama' },
{ name: 'tongyi', value: 'tongyi' },
{ name: 'claude', value: 'claude' },
]
const questions = [
{
type: 'list',
name: 'serviceType', //存储当前问题回答的变量key,
message: '请先选择服务类型',
choices: serveList,
},
]
function init() {
if (env.SERVICE_TYPE) {
// 判断env中SERVICE_TYPE是否配置和并且属于serveList数组中value的值
if (serveList.find((item) => item.value === env.SERVICE_TYPE)) {
handleStart(env.SERVICE_TYPE)
} else {
console.log('❌ 请正确配置.env文件中的 SERVICE_TYPE,或者删除该项')
}
} else {
inquirer
.prompt(questions)
.then((res) => {
handleStart(res.serviceType)
})
.catch((error) => {
console.log('❌ inquirer error:', error)
})
}
}
const program = new Command(name)
program
.alias('we')
.description('🤖一个基于 WeChaty 结合AI服务实现的微信机器人。')
.version(version, '-v, --version, -V')
.option('-s, --serve <type>', '跳过交互,直接设置启动的服务类型')
// .option('-p, --proxy <url>', 'proxy url', '')
.action(function () {
const { serve } = this.opts()
const args = this.args
if (!serve) return init()
handleStart(serve)
})
.command('start')
.option('-s, --serve <type>', '跳过交互,直接设置启动的服务类型', '')
.action(() => init())
// program
// .command('config')
// .option('-d, --depth <type>', 'Set the depth of the folder to be traversed', '10')
// .action(() => {
// // 打印当前项目的路径,而不是执行该文件时的所在路径
// console.log('请手动修改下面路径中的 config.json 文件')
// console.log(path.resolve(__dirname, '../.env'))
// })
program.parse()