160 lines
4.5 KiB
JavaScript
160 lines
4.5 KiB
JavaScript
/**
|
||
* 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,
|
||
};
|