Add smart ACK provider for WeChat MP replies
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>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user