diff --git a/.env.example b/.env.example index 4e5e51c2..2b2c3913 100644 --- a/.env.example +++ b/.env.example @@ -29,4 +29,20 @@ ALIAS_WHITELIST=微信名1,备注名2 ROOM_WHITELIST=XX群1,群2 # 默认服务 ChatGPT、Kimi、Xunfei、deepseek-free 四选一,不填则键盘交互 -SERVICE_TYPE='' \ No newline at end of file +SERVICE_TYPE='' + +# 通知间隔,单位为分钟,不填默认为30 +MASSAGE_INTERVAL=60 + +# 邮件通知 +MAIL_HOST=smtp.qq.com +MAIL_PORT=465 +MAIL_USERNAME=xxx@qq.com +#你的邮箱密码或授权码 +MAIL_PASSWORD=****** +#收件人邮箱 +MAIL_TO=*****@qq.com + +# 钉钉机器人 仅测试加签机器人 +DING_TOKEN=***** +DING_SIGN=****** \ No newline at end of file diff --git a/README.md b/README.md index 4691e9b0..769fa584 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,29 @@ ROOM_WHITELIST=XX群1,群2 ![](https://assets.fedtop.com/picbed/202212131123257.png) +4. 消息通知 + +- 运行报错通知 +- 登录扫码通知 + +目前仅支持邮件通知,钉钉机器人通知,后续会继续增加。 +.env文件填写对应的配置,不填则不会通知 + +```sh +# 邮件通知 +MAIL_HOST=smtp.qq.com +MAIL_PORT=465 +MAIL_USERNAME=xxx@qq.com +#你的邮箱密码或授权码 +MAIL_PASSWORD=****** +#收件人邮箱 +MAIL_TO=*****@qq.com + +# 钉钉机器人 仅测试加签机器人 +DING_TOKEN=***** +DING_SIGN=****** +``` + ## 常见问题 可以进交流群,一起交流探讨相关问题和解决方案,添加的时候记得备注来意。(如果项目对你有所帮助,也可以请我喝杯咖啡 ☕️ ~) diff --git a/package.json b/package.json index 9c339281..ff3f7cb8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "crypto-js": "^4.2.0", "dotenv": "^16.4.5", "inquirer": "^9.2.16", + "nodemailer": "^6.9.14", "openai": "^4.52.0", "p-timeout": "^6.0.0", "qrcode-terminal": "^0.12.0", diff --git a/src/index.js b/src/index.js index 63f3f81a..a4fd49e0 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ import fs from 'fs' import path, { dirname } from 'path' import { fileURLToPath } from 'url' import { defaultMessage } from './wechaty/sendMessage.js' +import { sendMessage } from './utils/sendMessage.js' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -20,6 +21,8 @@ function onScan(qrcode, status) { // 在控制台显示二维码 qrTerminal.generate(qrcode, { small: true }) const qrcodeImageUrl = ['https://api.qrserver.com/v1/create-qr-code/?data=', encodeURIComponent(qrcode)].join('') + sendMessage(qrcodeImageUrl, 'qr') + console.log('onScan:', qrcodeImageUrl, ScanStatus[status], status) } else { log.info('onScan: %s(%s)', ScanStatus[status], status) @@ -88,6 +91,7 @@ bot.on('friendship', onFriendShip) // 错误 bot.on('error', (e) => { console.error('❌ bot error handle: ', e) + sendMessage(e, 'error') // console.log('❌ 程序退出,请重新运行程序') // bot.stop() diff --git a/src/utils/sendMessage.js b/src/utils/sendMessage.js new file mode 100644 index 00000000..d9bcc980 --- /dev/null +++ b/src/utils/sendMessage.js @@ -0,0 +1,157 @@ +import dotenv from 'dotenv' +import CryptoJS from 'crypto-js' +import nodemailer from 'nodemailer' + +const env = dotenv.config().parsed // 环境参数 + +export function sendMessage(resultText, messageType = 'qr') { + if (checkDate()) { + return false + } + try { + switch (messageType) { + case 'qr': + sendMail(resultText, messageType) + dingSend(resultText, 'markdown') + break + case 'error': + sendMail(resultText, messageType) + dingSend(resultText, 'text') + break + default: + sendMail(resultText) + dingSend(resultText) + } + } catch (e) { + console.log(e) + } +} + +export function sendMail(resultText, messageType = 'text') { + // 环境变量判空 + if (!env.MAIL_HOST || !env.MAIL_USERNAME || !env.MAIL_PASSWORD || !env.MAIL_TO) { + return false + } + // 创建一个SMTP传输器 + const transporter = nodemailer.createTransport({ + host: env.MAIL_HOST, // QQ邮箱的SMTP服务器地址 + port: env.MAIL_HOST || 465, // 通常SMTP服务器使用的端口是465(SSL)或587(TLS) + secure: env.MAIL_HOST.toString() === '465', // 如果端口是465,则设置为true + auth: { + user: env.MAIL_USERNAME, // 你的邮箱地址 + pass: env.MAIL_PASSWORD, // 你的邮箱密码或授权码 + }, + }) + // 假设你有一个包含多个邮箱地址的字符串,用分号分隔 + const emailString = env.MAIL_TO + // 使用分号将字符串分割成数组 + const emailAddresses = emailString.split(';') + + // 设置邮件选项 + let mailOptions = { + from: env.MAIL_USERNAME, // 发件人邮箱地址 + to: emailAddresses, // 收件人邮箱地址 + subject: 'wechat-bot通知', // 邮件主题 + } + if (messageType === 'text') { + mailOptions.text = `【wechat-bot】:${resultText}` + } else if (messageType === 'error') { + mailOptions.html = `【wechat-bot报错】:${resultText}` + } else if (messageType === 'qr') { + mailOptions.html = `

【wechat-bot】微信扫码登录

Network Image` + } + + // 发送邮件 + transporter.sendMail(mailOptions, (error, info) => { + if (error) { + console.log(error) + } else { + console.log('Email sent: ' + info.response) + } + }) +} + +// 钉钉机器人 +export async function dingSend(resultText, msgtype = 'text') { + // 环境变量判空 + if (!env.DING_TOKEN) { + return false + } + // 发送钉钉消息 + try { + const timestamp = new Date().getTime() // 时间戳 + const secret = env.DING_SIGN // 钉钉机器人密钥 + let stringToSign = `${timestamp}\n${secret}` + let signature = CryptoJS.HmacSHA256(stringToSign, secret) + const hashInBase64 = CryptoJS.enc.Base64.stringify(signature) //base64加密 + const encodesign = encodeURI(hashInBase64) //解密 + // 钉钉机器人地址 + let url = `https://oapi.dingtalk.com/robot/send?access_token=${env.DING_TOKEN}×tamp=${timestamp}&sign=${encodesign}` + // 钉钉消息内容 + let text = { + msgtype: msgtype, + text: { + content: resultText, + }, + markdown: { + title: 'QR Code for your URL', + text: `### 【wechat-bot】微信扫码登录\n\n![Base64 Image](${resultText})`, + }, + } + let response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(text), + }) + let responseData = await response.json() + if (response.ok) { + console.log('钉钉消息发送成功,返回信息:', responseData) + } else { + console.log('钉钉消息发送失败', responseData) + } + } catch (error) { + console.log('钉钉消息发送失败', error) + } +} + +// 检测目录下是否有date.json文件,没有则创建,读取其中的date时间,超过30分钟则返回true +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +function checkDate() { + let massageInterval + try { + massageInterval = Number(env.MASSAGE_INTERVAL) || 30 // 默认30分钟 + } catch (e) { + massageInterval = 30 // 默认30分钟 + } + let date = new Date().getTime() // 当前时间 + const fileDir = path.resolve(__dirname, 'date.json') // 文件路径 + const dirPath = path.dirname(fileDir) // 获取目录路径 + + // 确保目录存在 + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }) + } + + if (fs.existsSync(fileDir)) { + // 读取文件内容 + const data = fs.readFileSync(fileDir, 'utf-8') + const jsonData = JSON.parse(data) + console.log('读取到date.json文件,内容为:', jsonData) + return (date - jsonData.time) / 60000 > massageInterval + } else { + // 创建date.json文件 + const obj = { time: date } + fs.writeFileSync(fileDir, JSON.stringify(obj), 'utf-8') + return true + } +} + +// 示例调用 +console.log(checkDate())