diff --git a/__tests__/index.html b/__tests__/index.html index ff40c8b..2fd2b24 100644 --- a/__tests__/index.html +++ b/__tests__/index.html @@ -6,7 +6,7 @@
diff --git a/db/chats.js b/db/chats.js index bb95abe..ce2368e 100644 --- a/db/chats.js +++ b/db/chats.js @@ -3,15 +3,12 @@ import moment from 'moment-timezone'; // Import moment-timezone for timezones // Set the default timezone to Beijing moment.tz.setDefault("Asia/Shanghai"); - - // 数据库文件名 const dbFile = 'chats.db'; - // 创建数据库连接 let db = new sqlite3.Database(dbFile, (err) => { if (err) { - console.error(err.message); + // console.error(err.message); } else { console.log('连接聊天记录数据库'); db.run(` @@ -29,37 +26,41 @@ let db = new sqlite3.Database(dbFile, (err) => { // 日志记录函数 export function chat(name, message) { - - // Limit message length to 200 characters - // if(!message) return - // const truncatedMessage = message.substring(0, 200); - const timestamp = moment().format("YYYY-MM-DD HH:mm"); // Format timestamp with Beijing time db.run(`INSERT INTO chats (timestamp, name, message) VALUES (?, ?, ?)`, [timestamp, name, message], function (err) { if (err) { - console.error(err.message); + // console.error(err.message); } }); - - // Keep only the latest 200 log entries db.run(`DELETE FROM chats WHERE id NOT IN (SELECT id FROM logs ORDER BY id DESC LIMIT 200)`); - - } - // 查询日志 export function getChats() { return new Promise((resolve, reject) => { db.all(`SELECT * FROM chats ORDER BY timestamp DESC`, [], (err, rows) => { if (err) { - console.error(err.message); + // console.error(err.message); reject(err); } else { resolve(rows); } }); }); +} + +// 删除所有聊天记录 +export function deleteChats() { + return new Promise((resolve, reject) => { + db.run(`DELETE FROM chats`, [], function(err) { + if (err) { + // console.error(err.message); + reject(err); + } else { + resolve({message: '所有日志已删除'}); // Resolve with a success message + } + }); + }); } \ No newline at end of file diff --git a/db/config.js b/db/config.js new file mode 100644 index 0000000..3e3415b --- /dev/null +++ b/db/config.js @@ -0,0 +1,126 @@ +import sqlite3 from 'sqlite3'; + +// 数据库文件名 +const dbFile = 'config.db'; // Use a separate DB file for config + +// Create the database connection +const db = new sqlite3.Database(dbFile, (err) => { + if (err) { + console.error(err.message); + } else { + console.log('配置文件数据库连接成功'); + // Create the config table if it doesn't exist + db.run(` + CREATE TABLE IF NOT EXISTS config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + difyApiKey TEXT, + geminiApiKey TEXT, + greeting TEXT, + pushTime TEXT, + groups TEXT, + isEnable INTEGER, + isPushEnable INTEGER + ) + `, (err) => { + if (err) { + console.error("Error creating config table:", err.message); + } else { + // Insert initial config if the table is empty + db.get("SELECT COUNT(*) AS count FROM config", [], (err, row) => { + if (err) { + console.error("Error checking config table:", err.message); + } else if (row.count === 0) { + const defaultConfig = { + difyApiKey: '', + geminiApiKey: '', + greeting: 'Hello!', + pushTime: '09:00', // Example default time + groups: '[]', // Example default groups + isEnable: 1, // 1 for true, 0 for false + isPushEnable: 1, // 1 for true, 0 for false + + }; + insertConfig(defaultConfig) // Use the insertConfig function + .then(() => console.log("写入默认配置")) + .catch(err => console.error("写入数据错误:", err)); + } + }); + } + }); + + } +}); + + + +// Function to insert or update config (upsert) +export function saveConfig(config) { + return new Promise((resolve, reject) => { + db.get("SELECT COUNT(*) AS count FROM config", [], (err, row) => { + if (err) { + reject(err); + } else if (row.count === 0) { + // Insert if no config exists + insertConfig(config).then(resolve).catch(reject); + } else { + // Update if config already exists (assuming only one config row) + updateConfig(config).then(resolve).catch(reject); + } + }); + }); +} + + + +// Helper function to insert config +function insertConfig(config) { + const { difyApiKey, geminiApiKey,greeting, groups, pushTime, isEnable,isPushEnable } = config; + return new Promise((resolve, reject) => { + db.run(`INSERT INTO config (difyApiKey,geminiApiKey, greeting, groups,pushTime, isEnable, isPushEnable) VALUES (?, ?, ?, ?, ?, ?, ?)`, + [difyApiKey, geminiApiKey,greeting, groups,pushTime, isEnable ? 1 : 0, isPushEnable ? 1 : 0], + function (err) { + if (err) { + reject(err); + } else { + resolve(this.lastID); + } + }); + }); +} + +// Helper function to update config +function updateConfig(config) { + const { difyApiKey, geminiApiKey,greeting, groups, pushTime, isEnable,isPushEnable } = config; + return new Promise((resolve, reject) => { + db.run(`UPDATE config SET difyApiKey = ?,geminiApiKey= ?, greeting = ?, groups = ?, pushTime = ?, isEnable = ? ,isPushEnable = ? WHERE id = 1`, // 更新第一列 + [difyApiKey, geminiApiKey,greeting, groups, pushTime, isEnable ? 1 : 0,isPushEnable ? 1 : 0], + function (err) { + if (err) { + reject(err); + } else { + resolve(this.changes); + } + }); + }); +} + + + +// Get config +export function getConfig() { + return new Promise((resolve, reject) => { + db.get(`SELECT * FROM config WHERE id = 1`, [], (err, row) => { // Select the first row + if (err) { + reject(err); + } else if (!row) { + resolve(null); // No config found. + } else { + + // Convert isEnable back to boolean + row.isEnable = !!row.isEnable; + row.isPushEnable = !!row.isPushEnable; + resolve(row); + } + }); + }); +} \ No newline at end of file diff --git a/db/logs.js b/db/logs.js index eb902de..de79974 100644 --- a/db/logs.js +++ b/db/logs.js @@ -1,61 +1,51 @@ import sqlite3 from 'sqlite3'; -import moment from 'moment-timezone'; // Import moment-timezone for timezones +import moment from 'moment-timezone'; -// Set the default timezone to Beijing moment.tz.setDefault("Asia/Shanghai"); - -// 数据库文件名 const dbFile = 'logs.db'; -// 创建数据库连接 -let db = new sqlite3.Database(dbFile, (err) => { +const db = new sqlite3.Database(dbFile, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => { if (err) { - console.error(err.message); + console.error('Error opening database:', err.message); } else { - console.log('连接日志数据库'); - // 创建日志表(如果不存在) + console.log('连接日志数据库成功'); + // Create the logs table db.run(` - CREATE TABLE IF NOT EXISTS logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Use STRFTIME for timestamps - level TEXT, - message TEXT - ) - `); + CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + timestamp TEXT, -- Store timestamp as TEXT for simplicity + level TEXT, + message TEXT + ) + `, (err) => { // Handle table creation errors + if (err) { + console.error("Error creating logs table:", err.message); + } + }); } }); - -// 日志记录函数 +// 记录日志 export function log(level, message) { - - // Limit message length to 200 characters - // if(!message) return - // const truncatedMessage = message.substring(0, 200); - - const timestamp = moment().format("YYYY-MM-DD HH:mm"); // Format timestamp with Beijing time - db.run(`INSERT INTO logs (timestamp, level, message) VALUES (?, ?, ?)`, [timestamp, level, message], function (err) { - if (err) { - console.error(err.message); - } + const timestamp = moment().format("YYYY-MM-DD HH:mm:ss"); // Add seconds + db.serialize(() => { // Ensure operations are executed in order + db.run(`INSERT INTO logs (timestamp, level, message) VALUES (?, ?, ?)`, [timestamp, level, message], (err) => { + if (err) { + console.error('Error inserting log:', err.message); // Log insertion errors + } + }); }); - - - // Keep only the latest 200 log entries - db.run(`DELETE FROM logs WHERE id NOT IN (SELECT id FROM logs ORDER BY id DESC LIMIT 200)`); - - } - // 查询日志 export function getLogs() { return new Promise((resolve, reject) => { db.all(`SELECT * FROM logs ORDER BY timestamp DESC`, [], (err, rows) => { // Order by timestamp descending if (err) { + console.error("触发错误"); console.error(err.message); reject(err); } else { @@ -63,4 +53,18 @@ export function getLogs() { } }); }); +} + +// 删除所有日志 +export function deleteLogs() { + return new Promise((resolve, reject) => { + db.run(`DELETE FROM logs`, [], function(err) { + if (err) { + console.error(err.message); + reject(err); + } else { + resolve({message: '所有日志已删除'}); // Resolve with a success message + } + }); + }); } \ No newline at end of file diff --git a/db/users.js b/db/users.js new file mode 100644 index 0000000..74a2b3d --- /dev/null +++ b/db/users.js @@ -0,0 +1,160 @@ +import sqlite3 from 'sqlite3'; +import bcrypt from 'bcrypt'; // 引入 bcrypt 用于密码加密 + +// 数据库文件名 +const dbFile = 'users.db'; // 使用 users.db 存储用户信息 + + +// 创建数据库连接 +const db = new sqlite3.Database(dbFile, (err) => { + if (err) { + // console.error(err.message); + } else { + console.log('连接用户数据库'); + // 创建用户表(如果不存在) + db.run(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + level TEXT NOT NULL + ) + `, (err) => { + if (err) { + // console.error("创建用户表失败:", err.message); + } else { + // 检查初始用户是否存在,如果不存在则创建 + db.get(`SELECT * FROM users WHERE username = 'admin'`, [], (err, row) => { + if (err) { + // console.error("查询初始用户失败:", err.message); + } else if (!row) { + // 创建初始用户 admin,密码加密 + bcrypt.hash('123456', 10, (err, hash) => { // 使用 bcrypt 加密密码 + if (err) { + // console.error("密码加密失败:", err.message); + } else { + db.run(`INSERT INTO users (username, password, level) VALUES (?, ?, ?)`, ['admin', hash, 'admin'], (err) => { + if (err) { + // console.error("创建初始用户失败:", err.message); + } else { + console.log("初始用户 admin 创建成功"); + } + }); + } + }); + } + }); + } + + }); + } +}); + + + +// 添加用户 +export async function addUser(username, password, level) { + return new Promise((resolve, reject) => { + (async () => { + try { + const hashedPassword = await bcrypt.hash(password, 10); // 加密密码 + db.run('INSERT INTO users (username, password, level) VALUES (?, ?, ?)', [username, hashedPassword, level], function(err) { + if (err) { + reject(err); + } else { + resolve(this.lastID); // 返回新用户的 ID + } + }); + } catch (error) { + reject(error); + } + })(); + }); + } + + + +// 获取所有用户 +export function getUsers() { + return new Promise((resolve, reject) => { + db.all('SELECT * FROM users', [], (err, rows) => { + if (err) { + reject(err); + } else { + resolve(rows); + } + }); + }); + } + + + +// 更新用户信息 +export async function updateUser(id, username, password, level) { + return new Promise((resolve, reject) => { + (async () => { + try { + // Only hash the password if it's provided + const data = [username, level, id]; + let sql = 'UPDATE users SET username = ?, level = ? WHERE id = ?'; + + if (password) { + const hashedPassword = await bcrypt.hash(password, 10); + data.splice(1, 0, hashedPassword); // Insert hashed password at the correct position + sql = 'UPDATE users SET username = ?, password = ?, level = ? WHERE id = ?'; + } + + db.run(sql, data, function(err) { + if (err) { + reject(err); + } else { + resolve(this.changes); // Return the number of changed rows + } + }); + } catch (error) { + reject(error); + } + })(); + }); + } + + +// 删除用户 +export function deleteUser(id) { + return new Promise((resolve, reject) => { + db.run('DELETE FROM users WHERE id = ?', id, function(err) { + if (err) { + reject(err); + } else { + resolve(this.changes); // Return the number of changed rows + } + }); + }); + } + + + +// 验证用户登录 +export async function verifyUser(username, password) { + return new Promise((resolve, reject) => { + db.get('SELECT * FROM users WHERE username = ?', [username], async function(err, row) { + if (err) { + reject(err); + } else if (!row) { + resolve(null); // 用户不存在 + } else { + try { + const match = await bcrypt.compare(password, row.password); + if (match) { + resolve(row); // 密码匹配,返回用户信息 + } else { + resolve(null); // 密码不匹配 + } + } catch (error) { + reject(error); // bcrypt 比较出错 + } + } + }); + }); + +} \ No newline at end of file diff --git a/index.js b/index.js index e1f5e35..374ecb0 100644 --- a/index.js +++ b/index.js @@ -13,15 +13,22 @@ import { WebSocketServer } from "ws" import express from 'express'; import path from 'path'; import open from 'open'; -import {log,getLogs} from './db/logs.js' -import {chat,getChats} from './db/chats.js' +import {log,getLogs,deleteLogs} from './db/logs.js' +import {chat,getChats,deleteChats} from './db/chats.js' +import {getConfig,saveConfig} from './db/config.js' import { fileURLToPath } from 'url'; // Import fileURLToPath const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const wss = new WebSocketServer({ port: 1982 }) +const wss = new WebSocketServer({ port: 1983 }) +// 限制 WebSocketServer 的最大连接数 +wss.setMaxListeners(20); // 或者设置为更合适的数值 + const app = express(); +app.use(express.json()); // 解析 JSON 格式的请求体 +app.use(express.urlencoded({ extended: true })); // 解析 URL 编码的请求体 + const port = 3000; config(); @@ -281,6 +288,13 @@ export async function prepareBot() { }) }) + // wss.on('connection', function connection(ws) { + // bot.on('scan', (qrcode) => { // 将 scan 事件处理程序放在 connection 内部 + // ws.send(qrcode); + // }); + // }); + + bot.on("login", (user) => { let {payload} = user; let {name} = payload; @@ -294,6 +308,7 @@ export async function prepareBot() { }) bot.on("error", (e) => { + console.log(e) log('error', e); }) // 启动的时间会比较久 @@ -337,16 +352,54 @@ async function startBot() { res.json(data); }); }); + // 删除全部日志 + app.delete('/api/logs', (req, res) => { + deleteLogs().then((data) => { + res.json(data); + }); + }); + // 删除全部聊天记录 + app.delete('/api/chats', (req, res) => { + deleteChats().then((data) => { + res.json(data); + }); + }); + // 获取全部聊天记录 app.get('/api/chats', (req, res) => { getChats().then((data) => { res.json(data); }); }); + // 获取配置信息 + app.get('/api/settings', (req, res) => { + getConfig().then((data) => { + res.json(data); + }); + }); + // 更新配置信息 + app.post('/api/settings', (req, res) => { + const config = req.body; + saveConfig(config).then((data) => { + res.json(data); + }); + }); app.get('/settings', (req, res) => { res.sendFile(path.join(__dirname, './public/settings.html')); }); - app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, './public/index.html')); + app.get('/docs', (req, res) => { + res.sendFile(path.join(__dirname, './public/docs.html')); + }); + app.get('/poster', (req, res) => { + res.sendFile(path.join(__dirname, './public/poster.html')); + }); + app.get('/chats', (req, res) => { + res.sendFile(path.join(__dirname, './public/chats.html')); + }); + app.get('/logs', (req, res) => { + res.sendFile(path.join(__dirname, './public/logs.html')); + }); + app.get('/knowledge', (req, res) => { + res.sendFile(path.join(__dirname, './public/knowledge.html')); }); app.listen(port, async () => { console.log(`Web server listening at http://localhost:${port}`); @@ -364,6 +417,7 @@ process.on('exit', async(code) => { process.on('SIGINT', async () => { log('warning', "程序退出"); + wss.close(); // 关闭 WebSocket 服务器 await bot.logout(); process.exit(); }); diff --git a/package.json b/package.json index 62c9434..a7da485 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "openai": "^4.28.0", "pdf-parse": "^1.1.1", "qrcode-terminal": "^0.12.0", + "qrcode.react": "^4.2.0", "replicate": "^0.34.0", "sensitive-words-js": "^1.0.4", "sqlite3": "^5.1.7", diff --git a/public/chats.html b/public/chats.html new file mode 100644 index 0000000..640e4d1 --- /dev/null +++ b/public/chats.html @@ -0,0 +1,219 @@ + + + + + + + TubeX微信机器人 + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/public/docs.html b/public/docs.html new file mode 100644 index 0000000..5c725a8 --- /dev/null +++ b/public/docs.html @@ -0,0 +1,213 @@ + + + + + + + TubeX微信机器人 + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/public/index.html b/public/index.html index 7b38363..6f7ea81 100644 --- a/public/index.html +++ b/public/index.html @@ -1,190 +1,219 @@ - + + - TubeX微信机器人 - - - -
-

TubeX微信机器人

-
-

聊天记录

- - - - - - - - - - -
ID时间戳用户名/群名消息
-

日志记录

- - - - - - - - - - -
ID时间戳等级消息
-
- - + + + + + + + + + + + + + + +
+ + - async function getLogs() { - try { - const response = await fetch('/api/logs'); // Make sure this route is correct - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const logs = await response.json(); - displayLogs(logs); - } catch (error) { - console.error("Error retrieving or displaying logs:", error); - // You could also display an error message on the page here. - } - } - async function getChats() { - try { - const response = await fetch('/api/chats'); // Make sure this route is correct - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const chats = await response.json(); - displayChats(chats); - } catch (error) { - console.error("Error retrieving or displaying logs:", error); - // You could also display an error message on the page here. - } - } - getLogs(); - getChats(); - - + + + + \ No newline at end of file diff --git a/public/knowledge.html b/public/knowledge.html new file mode 100644 index 0000000..ad8a280 --- /dev/null +++ b/public/knowledge.html @@ -0,0 +1,211 @@ + + + + + + + TubeX微信机器人 + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/public/logs.html b/public/logs.html new file mode 100644 index 0000000..8456279 --- /dev/null +++ b/public/logs.html @@ -0,0 +1,217 @@ + + + + + + + TubeX微信机器人 + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/public/poster.html b/public/poster.html new file mode 100644 index 0000000..dbcd1cd --- /dev/null +++ b/public/poster.html @@ -0,0 +1,204 @@ + + + + + + + TubeX微信机器人 + + + + + + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/public/settings.html b/public/settings.html index e5bda7e..472138a 100644 --- a/public/settings.html +++ b/public/settings.html @@ -14,7 +14,7 @@ - +