Browse Source

first commit

Pchen. 1 year ago
commit
ed2b7598e4

+ 109 - 0
README.md

@@ -0,0 +1,109 @@
+<h1 align="center">Web Wechat Bot</h1>
+
+「Web Wechat Bot」是一个基于Wechaty、可通过网页远程登录管理、可接入ChatGPT的微信聊天机器人,使用微信网页版协议。
+
+
+
+## 🖥主要技术构成
+
+**前端主要技术栈**
+
+- Vue3
+- Vue-router
+- ElementPlus
+
+**后端主要技术栈**
+
+- Node.js
+
+- Express
+
+- Wechaty
+
+- Sqlite3
+
+  
+
+## 💡项目文件结构
+
+```
+WebWechatBot                            
+├─ db                                   
+│  └─ data.db
+├─ public                               
+│  ├─ css                               
+│  ├─ js                                
+│  └─ index.html                        
+├─ wechat                               
+│  ├─ avatar                           
+│  ├─ getmessage.js                     
+│  └─ main.js                           
+├─ app.js                               
+├─ config.js                            
+├─ package.json                         
+├─ README.md                            
+└─ router.js			
+```
+
+
+
+## 💽Setup
+
+本项目可在 Linux、MacOS、Windows 系统上运行(在Linux服务器上可实现长期运行、远程控制管理)
+
+#### 0.安装Node.js
+
+Wechaty要求Node.js版本高于10,如果你还没有安装Node.js 或者你的版本低于10, 请参考下面的链接安装最新版本的Node.js:
+
+[Node.js](https://nodejs.org/en/download/package-manager/)
+
+#### 1.克隆远程库
+
+```
+git clone https://github.com/Pchen0/Web-Wechat-Bot.git
+```
+
+#### 2.安装依赖
+
+首先进入项目目录
+
+```
+cd Web-Wechat-Bot
+```
+
+安装依赖
+
+```
+npm install
+```
+
+#### 3.运行服务器
+
+```
+node app.js
+```
+
+运行之前可在config.js文件中修改项目运行的端口,记得在防火墙或安全组放行端口
+
+```js
+module.exports = {
+    port: 8080	//默认在8080端口上运行
+}
+```
+
+#### 4.配置
+
+通过你的ip地址+端口号进入到机器人的管理界面,默认用户名为`admin`,密码`123456`
+
+在API设置页面中填入你的接口地址、API Key、模型名称等,其中app_code专为一些兼容openai接口的平台设置,可不填。
+
+![](./images/1.png)
+
+在Wechat Bot设置界面你可以设置机器人的一些回复规则。
+
+![](./images/2.png)
+
+至此,本项目的搭建已接近尾声。进入微信登录页面,扫描二维码登录微信,登录成功后你的微信机器人就创建完成了。
+
+![](./images/3.png)
+

+ 20 - 0
app.js

@@ -0,0 +1,20 @@
+const express = require('express')
+
+const router = require('./router.js')
+const config = require('./config.js')
+
+const app = express()
+
+// 导入并配置cors中间件, 解决跨域
+const cors = require('cors')
+app.use(cors())
+
+router.use(express.static('./public'));
+
+app.use(express.json())
+
+app.use(router)
+
+app.listen(config.port,() =>{
+    console.log('程序正在',config.port,'端口上运行')
+})

+ 4 - 0
config.js

@@ -0,0 +1,4 @@
+module.exports = {
+    port: 8080
+}
+

BIN
db/data.db


BIN
images/1.png


BIN
images/2.png


BIN
images/3.png


+ 11 - 0
package.json

@@ -0,0 +1,11 @@
+{
+  "dependencies": {
+    "axios": "^1.6.5",
+    "cors": "^2.8.5",
+    "express": "^4.18.2",
+    "express-jwt": "^8.4.1",
+    "jsonwebtoken": "^9.0.2",
+    "sqlite3": "^5.1.7",
+    "wechaty": "^1.20.2"
+  }
+}

File diff suppressed because it is too large
+ 0 - 0
public/css/app.e9c295cf.css


File diff suppressed because it is too large
+ 0 - 0
public/css/chunk-vendors.0e83c255.css


+ 1 - 0
public/index.html

@@ -0,0 +1 @@
+<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>webbot</title><script defer="defer" src="js/chunk-vendors.310ecf7c.js"></script><script defer="defer" src="js/app.7e5e693b.js"></script><link href="css/chunk-vendors.0e83c255.css" rel="stylesheet"><link href="css/app.e9c295cf.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but webbot doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because it is too large
+ 0 - 0
public/js/app.7e5e693b.js


File diff suppressed because it is too large
+ 0 - 0
public/js/app.7e5e693b.js.map


File diff suppressed because it is too large
+ 0 - 0
public/js/chunk-vendors.310ecf7c.js


File diff suppressed because it is too large
+ 0 - 0
public/js/chunk-vendors.310ecf7c.js.map


+ 252 - 0
router.js

@@ -0,0 +1,252 @@
+const express = require('express')
+const { sendMessageToAPI, setApiKey, setApiUrl, setapp_code ,setmodel} = require('./wechat/getmessage')
+const sqlite3 = require('sqlite3')
+const jsonwebtoken = require('jsonwebtoken')
+const path = require('path')
+const secretKey = 'co666'
+const {
+    wxlogin,
+    Status,
+    User,
+    setAutoReplySingle,
+    setwhiteRoom,
+    setatReply,
+    setkeyWords,
+    setblackName,
+    setSuffix,
+    setPrefix,
+    stopWx,
+    loadConfigValues
+} = require('./wechat/main')
+
+//sqlite数据库路径
+let sqliteDbPath = "./db/data.db"
+//打开数据库
+var db = new sqlite3.Database(sqliteDbPath)
+
+const router = express.Router()
+
+//托管静态资源文件目录
+router.use(express.static('./public'))
+
+
+// 定义中间件.unless指定哪些接口不需要进行token身份认证
+const { expressjwt: jwt } = require("express-jwt")
+const checkTokenMiddleware = jwt({ secret: secretKey, algorithms: ["HS256"] }).unless({
+    path: [/^\/userlogin/, /^\/register/,/^\/getavatar/],
+})
+
+// 验证token
+const errorcheckToken = (err, req, res, next) => {
+    if (err.name === 'UnauthorizedError') {
+        return res.send({ status: 401, msg: '请先登录' })
+    }
+    res.send({ status: 500, msg: '未知错误' })
+}
+
+// 封装验证Token和错误处理的函数
+const checkToken = (req, res, next) => {
+    checkTokenMiddleware(req, res, (err) => {
+        if (err) {
+            errorcheckToken(err, req, res, next)
+        } else {
+            next()
+        }
+    })
+}
+
+router.use(checkToken)
+
+//用户登录
+router.post('/userlogin', (req, res) => {
+    var username = req.body.username
+    var password = req.body.password
+
+    // 匹配密码
+    db.all('select * from user where username=?', username, function (err, row) {
+        if (err) res.send({ status: 500, msg: "数据库查询失败" })
+        else {
+            if (row == "") {
+                res.send({ status: 500, msg: "此用户不存在" })
+            } else {
+                if (row[0].password != password) {
+                    res.send({ status: 500, msg: "密码错误" })
+                } else {
+                    // 如果用户名存在且密码匹配,则登录成功。
+                    const tokenStr = jsonwebtoken.sign({ username: username }, secretKey, { expiresIn: '24h' })
+                    res.send({ status: 200, msg: "登录成功", token: "Bearer " + tokenStr })
+                }
+            }
+        }
+    })
+})
+
+//更改账户信息
+
+function findusername(req, res, next) {
+    // 从请求头中获取 Token
+    const token = req.headers['authorization']
+    jsonwebtoken.verify(token.split(' ')[1], secretKey, (err, decoded) => {
+        if (err) {
+            return res.send({ status: 401, msg: 'Token无效' })
+        } else {
+            req.username = decoded.username
+            next()
+        }
+    })
+}
+
+router.post('/getusername', findusername,(req,res) => {
+    const username = req.username
+    res.send({status: 200,msg:username})
+})
+
+router.post('/changeaccount', findusername, (req, res) => {
+    const username = req.username
+    const oldpassword = req.body.oldpassword
+    const newusername = req.body.newusername
+    const newpassword = req.body.newpassword
+    // 查询用户是否存在以及旧密码是否正确
+    db.get('SELECT * FROM user WHERE username=? AND password=?', [username, oldpassword], (err, row) => {
+        if (err) {
+            res.send({ status: 500, msg: "数据库查询失败" })
+        } else {
+            if (!row) {
+                res.send({ status: 500, msg: "用户名或密码错误" })
+            } else {
+                if (newusername.length<5){
+                    res.send({ status: 500, msg: "用户名不能小于5位" })
+                }   else {
+                    if (newpassword.length<6){
+                        res.send({ status: 500, msg: "密码不能小于6位" })
+                    }   else    {
+                        // 更新用户名和密码
+                        db.run('UPDATE user SET username=?, password=? WHERE username=?', [newusername, newpassword, username], (err) => {
+                            if (err) {
+                                res.send({ status: 500, msg: "更新账户信息失败" })
+                            } else {
+                                res.send({ status: 200, msg: "账户信息更新成功" })
+
+                            }
+                        })
+                    }
+                }
+            }
+        }
+    })
+})
+
+//获取二维码 启动bot
+router.get('/getqrcode',async(req,res) => {
+    wxlogin()
+        .then(qrcodeUrl =>{
+            res.send({ qrcode: qrcodeUrl })
+    })
+})
+
+// 发送头像图片文件
+router.get('/getavatar',async(req,res) => {
+    try {
+        const avatarFilePath = path.join(__dirname,'./wechat/avatar/avatar.jpg')
+        res.sendFile(avatarFilePath)
+    } catch(error) {
+        res.send({status:500,msg:'获取头像失败!' + error.message})
+    }
+})
+
+router.get('/getwxname', async (req, res) => {
+    res.send({ wxname: User.name })
+})
+
+//获取二维码状态
+router.get('/getstatus',async(req,res) => {
+    res.send({status:Status.status})
+})
+
+// 停止机器人
+router.get('/stop', async (req, res) => {
+    try {
+        stopWx()
+        res.send({ Status: 200, msg: '停止机器人成功' })
+    } catch (error) {
+        res.send({ Status: 500, msg: '停止机器人失败' + error })
+    }
+})
+
+//获取api设置
+router.post('/getapiconfig', async (req, res) => {
+    db.all('SELECT * FROM apiconfig', [], (err, rows) => {
+        if (err) {
+            res.send({ status: 500, msg: '查询失败!' })
+            return
+        }
+        res.send({ status: 200, msg: rows })
+    })
+})
+
+//设置api接口相关配置
+router.post('/apiconfig',async(req,res) => {
+    const { apiKey,apiUrl,app_code,model } = req.body
+    try {
+        setApiKey(apiKey)
+        setApiUrl(apiUrl)
+        setapp_code(app_code)
+        setmodel(model)
+        res.send({status: 200,msg: '设置成功!'})
+    } catch (error) {
+        res.send({status: 500, msg: '设置失败!'})
+    }
+})
+
+//获取机器人设置
+router.post('/getwxconfig', async (req, res) => {
+    db.all('SELECT * FROM wxconfig', [], (err, rows) => {
+        if (err) {
+            res.send({ status: 500, msg: '查询失败!' })
+            return
+        }
+        res.send({ status: 200, msg: rows })
+    })
+})
+
+//设置微信机器人
+router.post('/wxconfig',async(req,res) => {
+    const { autoReplySingle, autoReplyRoom, suffix, prefix, otherTypeReply ,atReply,keyWords,blackName,whiteRoom} = req.body
+    try {
+        setAutoReplySingle(autoReplySingle)
+        setSuffix(suffix)
+        setPrefix(prefix)
+        setwhiteRoom(whiteRoom)
+        setatReply(atReply)
+        setkeyWords(keyWords)
+        setblackName(blackName)
+        loadConfigValues()
+        res.send({ status: 200, msg: '设置成功!' })
+    } catch (error) {
+        res.send({ status: 500, msg: '设置失败!' })
+    }
+})
+
+//获取消息发送记录
+router.post('/messagehistory',async (req,res)=>{
+        db.all('SELECT * FROM message', [], (err, rows) => {
+            if (err) {
+                res.send({ status: 500, msg: '查询失败!' })
+                return
+            }
+            res.send({ status: 200, msg: rows })
+        }) 
+})
+
+//清空消息发送记录
+router.post('/clearmessage',async(req,res) => {
+    db.run('DELETE FROM message', (err) => {
+        if (err) {
+            res.send({ status: 500, msg: '删除失败!' })
+        } else {
+            res.send({ status: 200, msg: '删除成功!' })
+        }
+    })
+})
+
+module.exports = router

BIN
wechat/avatar/avatar.jpg


+ 100 - 0
wechat/getmessage.js

@@ -0,0 +1,100 @@
+const axios = require('axios')
+const config = require('../config') 
+const sqlite3 = require('sqlite3')
+
+//sqlite数据库路径
+let sqliteDbPath = "./db/data.db"
+//打开数据库
+var db = new sqlite3.Database(sqliteDbPath)
+
+function getConfigValue(configName) {
+    return new Promise((resolve, reject) => {
+        const query = 'SELECT value FROM apiconfig WHERE config = ?';
+        db.get(query, [configName], (err, row) => {
+            if (err) {
+                reject(err);
+            } else {
+                const configValue = row ? row.value : null;
+                // 处理字符串 'null',如果是 'null' 则返回 null
+                resolve(configValue === 'null' ? null : configValue)
+            }
+        });
+    });
+}
+
+// 读取配置信息并设置相应的变量
+async function loadConfigValues() {
+    try {
+        apiKey = await getConfigValue('apiKey')
+        apiUrl = await getConfigValue('apiUrl')
+        app_code = await getConfigValue('app_code')
+        suffix = await getConfigValue('suffix')
+        model = await getConfigValue('model')
+        console.log('api接口设置加载成功');
+    } catch (error) {
+        console.error('加载api接口设置失败!', error);
+    }
+}
+
+// 调用函数加载配置信息
+loadConfigValues()
+
+
+async function sendMessageToAPI(message) {
+    const requestData = {
+        app_code: app_code,
+        messages: [{ "role": "user", "content": message }],
+        model: model
+    }
+
+    const token = "Bearer " + apiKey
+
+    try {
+        const responseData = await axios.post(apiUrl, requestData, {
+            headers: { 'Content-Type': 'application/json', Authorization: token }
+        })
+
+        const apiData = responseData.data
+        const apiMessage = apiData.choices[0].message.content
+
+        return apiMessage
+    } catch (error) {
+        console.error("向api接口发送请求时出现错误")
+        return error
+    }
+}
+
+// 更新api设置到数据库
+function updateapiConfigValue(configName, configValue) {
+    const query = 'REPLACE INTO apiconfig (config, value) VALUES (?, ?)';
+    db.run(query, [configName, configValue], (err) => {
+        if (err) {
+            console.error('更新数据失败:', err);
+        } else {
+            console.log('更新数据成功');
+        }
+    });
+}
+
+// 设置是否自动回复
+function setApiKey(value) {
+    apiKey = value;
+    updateapiConfigValue('apiKey', value);
+}
+
+function setApiUrl(value) {
+    apiUrl= value;
+    updateapiConfigValue('apiUrl', value);
+}
+
+function setapp_code(value) {
+    app_code = value;
+    updateapiConfigValue('app_code', value);
+}
+
+function setmodel(value) {
+    model = value
+    updateapiConfigValue('model', value);
+}
+
+module.exports = { sendMessageToAPI, setApiKey, setApiUrl, setapp_code , setmodel}

+ 297 - 0
wechat/main - 副本.js

@@ -0,0 +1,297 @@
+const { WechatyBuilder } = require("wechaty")
+const { sendMessageToAPI } = require('./getmessage')
+const sqlite3 = require('sqlite3')
+
+//sqlite数据库路径
+let sqliteDbPath = "./db/data.db"
+//打开数据库
+let db = new sqlite3.Database(sqliteDbPath)
+
+const wechaty = WechatyBuilder.build()
+
+function getConfigValue(configName) {
+    return new Promise((resolve, reject) => {
+        const query = 'SELECT value FROM wxconfig WHERE config = ?'
+        db.get(query, [configName], (err, row) => {
+            if (err) {
+                reject(err)
+            } else {
+                const configValue = row ? row.value : null
+                // 处理字符串 'null',如果是 'null' 则返回 null
+                resolve(configValue === 'null' ? null : configValue)
+            }
+        })
+    })
+}
+
+// 读取配置信息并设置相应的变量
+async function loadConfigValues() {
+    try {
+        autoReplySingle = await getConfigValue('autoReplySingle') === 'true'
+        prefix = await getConfigValue('prefix')
+        suffix = await getConfigValue('suffix')
+        whiteRoomString = await getConfigValue('whiteRoom')
+        keyWordsString = await getConfigValue('keyWords')
+        blackNameString = await getConfigValue('blackName')
+        atReply = await getConfigValue('atReply') === 'true'
+
+        // 处理转义符
+        suffix = suffix !== null ? suffix.replace(/\\n/g, '\n') : ''
+        otherTypeReply = otherTypeReply !== null ? otherTypeReply.replace(/\\n/g, '\n') : ''
+        prefix = prefix !== null ? prefix.replace(/\\n/g, '\n') : ''
+
+        // 处理用逗号分隔的字符串形式的数组
+        whiteRoom = whiteRoomString !== null ? whiteRoomString.split(",").map(item => item.trim()) : []
+        keyWords = keyWordsString !== null ? keyWordsString.split(",").map(item => item.trim()) : []
+        blackName = blackNameString !== null ? blackNameString.split(",").map(item => item.trim()) : []
+
+        console.log('Config values loaded successfully.')
+    } catch (error) {
+        console.error('Error loading config values:', error)
+    }
+}
+
+
+
+// 调用函数加载配置信息
+loadConfigValues()
+
+//获取时间
+function getCurrentTime() {
+    const options = {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit',
+    }
+
+    const currentTime = new Date().toLocaleString('zh-CN', options)
+    return currentTime
+}
+
+//停止函数运行
+let isRunning = false
+
+async function stopWx() {
+    if (isRunning) {
+        isRunning = false
+        await wechaty.stop();
+        Status.status = 0;
+        console.log('Wechaty stopped.');
+    } else {
+        console.log('Wechaty is not running.');
+    }
+}
+
+
+let Status = { status: null }
+let User = {name: null}
+
+
+async function wxlogin() {
+    if (isRunning) {
+        isRunning = false
+        await wechaty.stop();
+        Status.status = 0;
+        console.log('Wechaty stopped.');
+    } else {
+        console.log('Wechaty is not running.');
+    }
+
+    isRunning = true
+    return new Promise((resolve, reject) => {
+        let qrcodeUrl
+
+        // 解除之前绑定的所有事件处理程序
+        wechaty.removeAllListeners();
+
+        wechaty
+            .on('scan', (qrcode, status) => {
+                qrcodeUrl = `https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?text=${encodeURIComponent(qrcode)}`
+                console.log('Scan QR Code to log in:', status)
+                Status.status = status
+                // 将 qrcodeUrl 提前返回
+                resolve(qrcodeUrl)
+            })
+
+            .on('login', async (user) => {
+                console.log(`User ${user} logged in`);
+                Status.status = 200
+                // 获取登录用户的信息
+                const contact = await wechaty.Contact.find({ id: user.id });
+                const name = await contact.name();
+                const avatarFileBox = await contact.avatar();
+                User.name = name
+                // 将头像保存到本地
+                const avatarFilePath = `./wechat/avatar/avatar.jpg`
+                await avatarFileBox.toFile(avatarFilePath,true)
+                // 有程序运行后配置未加载的问题,这里重新加载一遍
+                loadConfigValues()
+            })
+
+            .on('logout', async () => {
+                Status.status = null
+                isRunning = false
+                await wechaty.stop()
+            })
+
+            .on('message',async (message) => {
+                if (message.type() === wechaty.Message.Type.Text) {
+                    const content = message.text()
+                    console.log(content)
+                    const room = message.room()
+                    const talker = message.talker()
+                    const talkername = message.talker().payload.name
+                    const foundWords = keyWords.filter(word => content.includes(word))
+
+                    if (room) {
+                        const roomname = message.room().payload.topic
+                        if (whiteRoom.length === 0 || whiteRoom.includes(roomname)) {
+                            //在群聊中被@
+                            if (await message.mentionSelf()) {
+                                console.log('机器人被@')
+                                if (atReply) {
+                                    const apiMessage = await sendMessageToAPI(content)
+                                    const senmsg = '@' + talkername + '  ' + prefix + apiMessage + suffix
+                                    room.say(senmsg)
+                                    //写入数据库
+                                    writeToDatabase({
+                                        time: getCurrentTime(),
+                                        type: '群聊',
+                                        recmsg: content,
+                                        senmsg: senmsg,
+                                        name: talkername,
+                                        roomname: roomname,
+                                    })
+                                    return
+                                }
+                            } else if (foundWords.length > 0) {
+                                console.log('发现关键字')
+                                const apiMessage = await sendMessageToAPI(content)
+                                const senmsg = '@' + talkername + '  ' + prefix + apiMessage + suffix
+                                room.say(senmsg)
+                                //写入数据库
+                                writeToDatabase({
+                                    time: getCurrentTime(),
+                                    type: '群聊',
+                                    recmsg: content,
+                                    senmsg: senmsg,
+                                    name: talkername,
+                                    roomname: roomname,
+                                })
+                                return
+                            }
+                        } else {
+                            return
+                        }
+
+                    } else {
+                        if (autoReplySingle) {
+                             if (blackName.includes(talkername)) {
+                                console.log('发送者在黑名单中')
+                                return
+                             } else {
+                                const apiMessage = await sendMessageToAPI(content)
+                                const senmsg = prefix + apiMessage + suffix
+                                talker.say(senmsg)
+                                //向数据库写入消息
+                                writeToDatabase({
+                                    time: getCurrentTime(),
+                                    type: '私聊',
+                                    recmsg: content,
+                                    senmsg: senmsg,
+                                    name: message.talker().payload.name,
+                                    roomname: null,
+                                })
+                                return
+                            }
+                        }
+                    }
+                } else {
+                    console.log('不受支持的消息类型')
+                    return
+                } 
+            }
+        )
+        wechaty.start()
+        
+        wechaty.on('error', (error) => {
+            reject(error)
+        })
+    })
+}
+
+//向数据库写入数据
+function writeToDatabase(data) {
+    const { time, type, recmsg, senmsg, name, roomname } = data
+
+    const insertQuery = `INSERT INTO message (time, type, recmsg, senmsg, name, roomname) VALUES (?, ?, ?, ?, ?, ?)`
+    db.run(insertQuery, [time, type, recmsg, senmsg, name, roomname], (error) => {
+        if (error) {
+            console.error('数据库写入失败:', error)
+        } else {
+            console.log('数据库写入成功')
+        }
+    })
+}
+
+// 更新设置到数据库
+function updateConfigValue(configName, configValue) {
+    const query = 'INSERT OR REPLACE INTO wxconfig (config, value) VALUES (?, ?)'
+    db.run(query, [configName, configValue], (err) => {
+        if (err) {
+            console.error('数据库写入失败:', err)
+        } else {
+            console.log('数据库写入成功')
+        }
+    })
+}
+
+// 设置是否自动回复
+function setAutoReplySingle(value) {
+    updateConfigValue('autoReplySingle', value)
+}
+
+function setatReply(value) {
+    updateConfigValue('atReply', value)
+}
+
+function setblackName(value) {
+    updateConfigValue('blackName', value)
+}
+
+function setkeyWords(value) {
+    updateConfigValue('keyWords', value)
+}
+
+function setwhiteRoom(value) {
+    updateConfigValue('whiteRoom', value)
+}
+
+// 修改前缀
+function setSuffix(value) {
+    updateConfigValue('suffix', value)
+}
+
+// 修改后缀
+function setPrefix(value) {
+    updateConfigValue('prefix', value)
+}
+
+
+module.exports = {
+    wxlogin,
+    Status,
+    setAutoReplySingle,
+    setwhiteRoom,
+    setatReply,
+    setkeyWords,
+    setblackName,
+    setSuffix,
+    setPrefix,
+    stopWx,
+    loadConfigValues,
+    User
+}

+ 302 - 0
wechat/main.js

@@ -0,0 +1,302 @@
+const { WechatyBuilder } = require("wechaty")
+const { sendMessageToAPI } = require('./getmessage')
+const sqlite3 = require('sqlite3')
+
+//sqlite数据库路径
+let sqliteDbPath = "./db/data.db"
+//打开数据库
+let db = new sqlite3.Database(sqliteDbPath)
+
+const wechaty = WechatyBuilder.build()
+
+function getConfigValue(configName) {
+    return new Promise((resolve, reject) => {
+        const query = 'SELECT value FROM wxconfig WHERE config = ?'
+        db.get(query, [configName], (err, row) => {
+            if (err) {
+                reject(err)
+            } else {
+                const configValue = row ? row.value : null
+                // 处理字符串 'null',如果是 'null' 则返回 null
+                resolve(configValue === 'null' ? null : configValue)
+            }
+        })
+    })
+}
+
+// 读取配置信息并设置相应的变量
+async function loadConfigValues() {
+    try {
+        autoReplySingle = await getConfigValue('autoReplySingle') === 'true'
+        prefix = await getConfigValue('prefix')
+        suffix = await getConfigValue('suffix')
+        whiteRoomString = await getConfigValue('whiteRoom')
+        keyWordsString = await getConfigValue('keyWords')
+        blackNameString = await getConfigValue('blackName')
+        atReply = await getConfigValue('atReply') === 'true'
+
+        // 处理转义符
+        suffix = suffix !== null ? suffix.replace(/\\n/g, '\n') : ''
+        prefix = prefix !== null ? prefix.replace(/\\n/g, '\n') : ''
+
+        // 处理用逗号分隔的字符串形式的数组
+        whiteRoom = whiteRoomString !== null ? whiteRoomString.split(",").map(item => item.trim()) : []
+        keyWords = keyWordsString !== null ? keyWordsString.split(",").map(item => item.trim()) : []
+        blackName = blackNameString !== null ? blackNameString.split(",").map(item => item.trim()) : []
+
+        console.log('Config values loaded successfully.')
+    } catch (error) {
+        console.error('Error loading config values:', error)
+    }
+}
+
+
+
+// 调用函数加载配置信息
+loadConfigValues()
+
+//获取时间
+function getCurrentTime() {
+    const options = {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit',
+    }
+
+    const currentTime = new Date().toLocaleString('zh-CN', options)
+    return currentTime
+}
+
+//停止函数运行
+let isRunning = false
+
+async function stopWx() {
+    if (isRunning) {
+        isRunning = false
+        await wechaty.stop();
+        Status.status = 0;
+        console.log('Wechaty stopped.');
+    } else {
+        console.log('Wechaty is not running.');
+    }
+}
+
+
+let Status = { status: null }
+let User = {name: null}
+
+
+async function wxlogin() {
+    if (isRunning) {
+        isRunning = false
+        await wechaty.stop();
+        Status.status = 0;
+        console.log('Wechaty stopped.');
+    } else {
+        console.log('Wechaty is not running.');
+    }
+
+    isRunning = true
+    return new Promise((resolve, reject) => {
+        let qrcodeUrl
+
+        // 解除之前绑定的所有事件处理程序
+        wechaty.removeAllListeners();
+
+        wechaty
+            .on('scan', (qrcode, status) => {
+                qrcodeUrl = `https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?text=${encodeURIComponent(qrcode)}`
+                console.log('Scan QR Code to log in:', status)
+                Status.status = status
+                // 将 qrcodeUrl 提前返回
+                resolve(qrcodeUrl)
+            })
+
+            .on('login', async (user) => {
+                console.log(`User ${user} logged in`);
+                Status.status = 200
+                // 获取登录用户的信息
+                const contact = await wechaty.Contact.find({ id: user.id });
+                const name = await contact.name();
+                const avatarFileBox = await contact.avatar();
+                User.name = name
+                // 将头像保存到本地
+                const avatarFilePath = `./wechat/avatar/avatar.jpg`
+                await avatarFileBox.toFile(avatarFilePath,true)
+                // 有程序运行后配置未加载的问题,这里重新加载一遍
+                loadConfigValues()
+            })
+
+            .on('logout', async () => {
+                Status.status = null
+                isRunning = false
+                await wechaty.stop()
+            })
+
+            .on('message',async (message) => {
+                if (message.self()) {
+                    console.log('自己发送消息:'+message.text())
+                    return
+                }   else  {
+                    if (message.type() === wechaty.Message.Type.Text) {
+                        const content = message.text()
+                        console.log(content)
+                        const room = message.room()
+                        const talker = message.talker()
+                        const talkername = message.talker().payload.name
+                        const foundWords = keyWords.filter(word => content.includes(word))
+
+                        if (room) {
+                            const roomname = message.room().payload.topic
+                            if (whiteRoom.length === 0 || whiteRoom.includes(roomname)) {
+                                //在群聊中被@
+                                if (await message.mentionSelf()) {
+                                    console.log('机器人被@')
+                                    if (atReply) {
+                                        const apiMessage = await sendMessageToAPI(content)
+                                        const senmsg = '@' + talkername + '  ' + prefix + apiMessage + suffix
+                                        room.say(senmsg)
+                                        //写入数据库
+                                        writeToDatabase({
+                                            time: getCurrentTime(),
+                                            type: '群聊',
+                                            recmsg: content,
+                                            senmsg: senmsg,
+                                            name: talkername,
+                                            roomname: roomname,
+                                        })
+                                        return
+                                    }
+                                } else if (foundWords.length > 0) {
+                                    console.log('发现关键字')
+                                    const apiMessage = await sendMessageToAPI(content)
+                                    const senmsg = '@' + talkername + '  ' + prefix + apiMessage + suffix
+                                    room.say(senmsg)
+                                    //写入数据库
+                                    writeToDatabase({
+                                        time: getCurrentTime(),
+                                        type: '群聊',
+                                        recmsg: content,
+                                        senmsg: senmsg,
+                                        name: talkername,
+                                        roomname: roomname,
+                                    })
+                                    return
+                                }
+                            } else {
+                                return
+                            }
+
+                        } else {
+                            if (autoReplySingle) {
+                                if (blackName.includes(talkername)) {
+                                    console.log('发送者在黑名单中')
+                                    return
+                                } else {
+                                    const apiMessage = await sendMessageToAPI(content)
+                                    const senmsg = prefix + apiMessage + suffix
+                                    talker.say(senmsg)
+                                    //向数据库写入消息
+                                    writeToDatabase({
+                                        time: getCurrentTime(),
+                                        type: '私聊',
+                                        recmsg: content,
+                                        senmsg: senmsg,
+                                        name: message.talker().payload.name,
+                                        roomname: null,
+                                    })
+                                    return
+                                }
+                            }
+                        }
+                    } else {
+                        console.log('不受支持的消息类型')
+                        return
+                    } 
+                }
+                
+            }
+        )
+        wechaty.start()
+        
+        wechaty.on('error', (error) => {
+            reject(error)
+        })
+    })
+}
+
+//向数据库写入数据
+function writeToDatabase(data) {
+    const { time, type, recmsg, senmsg, name, roomname } = data
+
+    const insertQuery = `INSERT INTO message (time, type, recmsg, senmsg, name, roomname) VALUES (?, ?, ?, ?, ?, ?)`
+    db.run(insertQuery, [time, type, recmsg, senmsg, name, roomname], (error) => {
+        if (error) {
+            console.error('数据库写入失败:', error)
+        } else {
+            console.log('数据库写入成功')
+        }
+    })
+}
+
+// 更新设置到数据库
+function updateConfigValue(configName, configValue) {
+    const query = 'INSERT OR REPLACE INTO wxconfig (config, value) VALUES (?, ?)'
+    db.run(query, [configName, configValue], (err) => {
+        if (err) {
+            console.error('数据库写入失败:', err)
+        } else {
+            console.log('数据库写入成功')
+        }
+    })
+}
+
+// 设置是否自动回复
+function setAutoReplySingle(value) {
+    updateConfigValue('autoReplySingle', value)
+}
+
+function setatReply(value) {
+    updateConfigValue('atReply', value)
+}
+
+function setblackName(value) {
+    updateConfigValue('blackName', value)
+}
+
+function setkeyWords(value) {
+    updateConfigValue('keyWords', value)
+}
+
+function setwhiteRoom(value) {
+    updateConfigValue('whiteRoom', value)
+}
+
+// 修改前缀
+function setSuffix(value) {
+    updateConfigValue('suffix', value)
+}
+
+// 修改后缀
+function setPrefix(value) {
+    updateConfigValue('prefix', value)
+}
+
+
+module.exports = {
+    wxlogin,
+    Status,
+    setAutoReplySingle,
+    setwhiteRoom,
+    setatReply,
+    setkeyWords,
+    setblackName,
+    setSuffix,
+    setPrefix,
+    stopWx,
+    loadConfigValues,
+    User
+}

Some files were not shown because too many files changed in this diff