/** * SQLite:统计、留言、后端配置(弹幕开关与位置) */ const Database = require('better-sqlite3'); const path = require('path'); const DB_PATH = path.join(__dirname, 'data', 'pano.db'); function getDb() { const db = new Database(DB_PATH); db.pragma('journal_mode = WAL'); return db; } function initDb() { const fs = require('fs'); const dir = path.join(__dirname, 'data'); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const db = getDb(); db.exec(` CREATE TABLE IF NOT EXISTS stats ( id INTEGER PRIMARY KEY CHECK (id = 1), view_count INTEGER NOT NULL DEFAULT 0, like_count INTEGER NOT NULL DEFAULT 0, share_count INTEGER NOT NULL DEFAULT 0, watching_now INTEGER NOT NULL DEFAULT 0 ); INSERT OR IGNORE INTO stats (id, view_count, like_count, share_count, watching_now) VALUES (1, 0, 0, 0, 0); CREATE TABLE IF NOT EXISTS viewers ( viewer_id TEXT PRIMARY KEY, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, nickname TEXT, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); INSERT OR IGNORE INTO settings (key, value) VALUES ('danmaku_enabled', '0'); INSERT OR IGNORE INTO settings (key, value) VALUES ('danmaku_position', 'top'); `); db.close(); } function getConfig() { const db = getDb(); const rows = db.prepare('SELECT key, value FROM settings').all(); db.close(); const map = {}; rows.forEach((r) => { map[r.key] = r.value; }); return { danmakuEnabled: map.danmaku_enabled === '1', danmakuPosition: (map.danmaku_position || 'top').toLowerCase(), }; } function getStats() { const db = getDb(); const row = db.prepare('SELECT view_count, like_count, share_count, watching_now FROM stats WHERE id = 1').get(); const commentRow = db.prepare('SELECT COUNT(*) as n FROM comments').get(); db.close(); return { viewCount: row.view_count, commentCount: commentRow.n, likeCount: row.like_count, shareCount: row.share_count, watchingNow: row.watching_now, }; } function incView() { const db = getDb(); db.prepare('UPDATE stats SET view_count = view_count + 1 WHERE id = 1').run(); const out = getStats(); db.close(); return out; } function incLike() { const db = getDb(); db.prepare('UPDATE stats SET like_count = like_count + 1 WHERE id = 1').run(); const row = db.prepare('SELECT like_count FROM stats WHERE id = 1').get(); db.close(); return { likeCount: row.like_count }; } function incShare() { const db = getDb(); db.prepare('UPDATE stats SET share_count = share_count + 1 WHERE id = 1').run(); const row = db.prepare('SELECT share_count FROM stats WHERE id = 1').get(); db.close(); return { shareCount: row.share_count }; } function joinViewer(viewerId) { const db = getDb(); const now = Date.now(); db.prepare('INSERT OR REPLACE INTO viewers (viewer_id, updated_at) VALUES (?, ?)').run(viewerId, now); db.prepare('DELETE FROM viewers WHERE updated_at < ?').run(now - 120000); const row = db.prepare('SELECT COUNT(*) as n FROM viewers').get(); db.prepare('UPDATE stats SET watching_now = ? WHERE id = 1').run(row.n); db.close(); return row.n; } function leaveViewer(viewerId) { const db = getDb(); db.prepare('DELETE FROM viewers WHERE viewer_id = ?').run(viewerId); const row = db.prepare('SELECT COUNT(*) as n FROM viewers').get(); db.prepare('UPDATE stats SET watching_now = ? WHERE id = 1').run(row.n); db.close(); return row.n; } function getComments(limit = 100) { const db = getDb(); const rows = db.prepare( 'SELECT id, content, nickname, created_at FROM comments ORDER BY id DESC LIMIT ?' ).all(limit); db.close(); return rows.reverse().map((r) => ({ id: r.id, content: r.content, nickname: r.nickname || '游客', createdAt: r.created_at, })); } function addComment(content, nickname) { const db = getDb(); const now = Date.now(); const r = db.prepare('INSERT INTO comments (content, nickname, created_at) VALUES (?, ?, ?)').run( String(content).trim().slice(0, 200) || '(空)', nickname ? String(nickname).trim().slice(0, 32) : null, now ); db.close(); return { id: r.lastInsertRowid, content: content.trim().slice(0, 200), nickname: nickname || '游客', createdAt: now }; } module.exports = { initDb, getConfig, getStats, incView, incLike, incShare, joinViewer, leaveViewer, getComments, addComment, };