82 lines
2.4 KiB
JavaScript
82 lines
2.4 KiB
JavaScript
'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 };
|