9b4a25799f
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
3.2 KiB
JavaScript
84 lines
3.2 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
export async function ensureWordFilterSchema(pool) {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS h5_blocked_words (
|
|
id CHAR(36) PRIMARY KEY,
|
|
word VARCHAR(200) NOT NULL,
|
|
replacement VARCHAR(200) NOT NULL DEFAULT '***',
|
|
note VARCHAR(500) NULL,
|
|
status ENUM('active', 'disabled') NOT NULL DEFAULT 'active',
|
|
created_at BIGINT NOT NULL,
|
|
updated_at BIGINT NOT NULL,
|
|
UNIQUE KEY uk_blocked_word (word)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
}
|
|
|
|
export function createWordFilterService(pool) {
|
|
async function listBlockedWords() {
|
|
const [rows] = await pool.query(
|
|
'SELECT id, word, replacement, note, status, created_at, updated_at FROM h5_blocked_words ORDER BY created_at DESC',
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
async function createBlockedWord({ word, replacement, note }) {
|
|
const trimmed = String(word ?? '').trim();
|
|
if (!trimmed) return { ok: false, message: '词语不能为空' };
|
|
const id = crypto.randomUUID();
|
|
const now = Date.now();
|
|
try {
|
|
await pool.query(
|
|
'INSERT INTO h5_blocked_words (id, word, replacement, note, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[id, trimmed, replacement ?? '***', note ?? '', 'active', now, now],
|
|
);
|
|
const [rows] = await pool.query('SELECT * FROM h5_blocked_words WHERE id = ?', [id]);
|
|
return { ok: true, blockedWord: rows[0] };
|
|
} catch (err) {
|
|
if (err?.code === 'ER_DUP_ENTRY') return { ok: false, message: '该词语已存在' };
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function updateBlockedWord(id, { word, replacement, note, status }) {
|
|
const updates = [];
|
|
const values = [];
|
|
if (word !== undefined) {
|
|
const trimmed = String(word).trim();
|
|
if (!trimmed) return { ok: false, message: '词语不能为空' };
|
|
updates.push('word = ?');
|
|
values.push(trimmed);
|
|
}
|
|
if (replacement !== undefined) { updates.push('replacement = ?'); values.push(replacement); }
|
|
if (note !== undefined) { updates.push('note = ?'); values.push(note); }
|
|
if (status !== undefined) { updates.push('status = ?'); values.push(status); }
|
|
if (!updates.length) return { ok: false, message: '没有可更新的字段' };
|
|
updates.push('updated_at = ?');
|
|
values.push(Date.now(), id);
|
|
const [result] = await pool.query(
|
|
`UPDATE h5_blocked_words SET ${updates.join(', ')} WHERE id = ?`,
|
|
values,
|
|
);
|
|
if (!result.affectedRows) return { ok: false, message: '记录不存在' };
|
|
const [rows] = await pool.query('SELECT * FROM h5_blocked_words WHERE id = ?', [id]);
|
|
return { ok: true, blockedWord: rows[0] };
|
|
}
|
|
|
|
async function deleteBlockedWord(id) {
|
|
const [result] = await pool.query('DELETE FROM h5_blocked_words WHERE id = ?', [id]);
|
|
if (!result.affectedRows) return { ok: false, message: '记录不存在' };
|
|
return { ok: true };
|
|
}
|
|
|
|
async function listAllForFrontend() {
|
|
const [rows] = await pool.query(
|
|
'SELECT word, replacement FROM h5_blocked_words WHERE status = ? ORDER BY word',
|
|
['active'],
|
|
);
|
|
return rows;
|
|
}
|
|
|
|
return { listBlockedWords, createBlockedWord, updateBlockedWord, deleteBlockedWord, listAllForFrontend };
|
|
}
|