feat: add online FAQ/KB sync without old bridge
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function stripHtml(value) {
|
||||
return String(value || '')
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function extractFaqFromLdJson(html, max = 50) {
|
||||
const re = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
|
||||
let m;
|
||||
while ((m = re.exec(String(html)))) {
|
||||
try {
|
||||
const data = JSON.parse(m[1].trim());
|
||||
const candidates = Array.isArray(data) ? data : [data];
|
||||
for (const item of candidates) {
|
||||
const main = item && (item.mainEntity || (item['@type'] === 'FAQPage' && item.mainEntity));
|
||||
if (!Array.isArray(main)) continue;
|
||||
const out = main
|
||||
.filter((q) => q && q.name && q.acceptedAnswer && q.acceptedAnswer.text)
|
||||
.map((q) => ({ q: stripHtml(q.name), a: stripHtml(q.acceptedAnswer.text) }))
|
||||
.filter((q) => q.q && q.a)
|
||||
.slice(0, max);
|
||||
if (out.length) return out;
|
||||
}
|
||||
} catch {
|
||||
// 非 JSON-LD 块忽略
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function loadFaqConfig(file) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
} catch {
|
||||
return { enabled: false, pages: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function createFaqFetcher(config = {}) {
|
||||
const pages = config.pages || [];
|
||||
const timeoutMs = config.timeoutMs || 15000;
|
||||
const max = config.maxFaqsPerPage || 50;
|
||||
|
||||
async function fetchPage(url) {
|
||||
const res = await fetch(url, {
|
||||
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36', Accept: 'text/html,application/xhtml+xml' },
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
if (!res.ok) throw new Error(`fetch ${url} failed: HTTP ${res.status}`);
|
||||
return extractFaqFromLdJson(await res.text(), max);
|
||||
}
|
||||
|
||||
async function fetchAll() {
|
||||
const out = [];
|
||||
for (const page of pages) {
|
||||
try {
|
||||
const questions = await fetchPage(page.url);
|
||||
out.push({ ...page, questions });
|
||||
} catch (err) {
|
||||
out.push({ ...page, questions: [], error: err.message });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return { fetchPage, fetchAll, pages };
|
||||
}
|
||||
|
||||
module.exports = { createFaqFetcher, loadFaqConfig, extractFaqFromLdJson, stripHtml };
|
||||
Reference in New Issue
Block a user