feat(brandbook): anadir ingesta automatica de fuentes desde URL web en seccion Fuentes

This commit is contained in:
Marcos Fargas
2026-10-15 01:14:38 +02:00
parent bcde8d8198
commit e4a605bdbd
2 changed files with 139 additions and 3 deletions

View File

@@ -55,15 +55,17 @@ export async function POST(req: NextRequest) {
let filename = 'pegado.txt';
let text = '';
let url = '';
const ctype = req.headers.get('content-type') || '';
if (ctype.includes('multipart/form-data')) {
const form = await req.formData();
const file = form.get('file');
const pasted = form.get('text');
if (typeof pasted === 'string' && pasted.trim()) {
url = String(form.get('url') || '').trim();
if (!url && typeof pasted === 'string' && pasted.trim()) {
text = pasted;
filename = String(form.get('filename') || 'pegado.txt');
} else if (file && typeof file === 'object' && 'arrayBuffer' in file) {
} else if (!url && file && typeof file === 'object' && 'arrayBuffer' in file) {
const f = file as File;
filename = f.name || 'upload.txt';
const lower = filename.toLowerCase();
@@ -87,11 +89,80 @@ export async function POST(req: NextRequest) {
const body = (await req.json().catch(() => ({}))) as {
text?: string;
filename?: string;
url?: string;
};
url = String(body.url || '').trim();
text = String(body.text || '');
filename = String(body.filename || 'pegado.txt');
}
if (url) {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return NextResponse.json(
{ error: 'La URL debe empezar por http:// o https://' },
{ status: 400 }
);
}
try {
const resp = await fetch(url, {
headers: {
'User-Agent':
'Mozilla/5.0 (compatible; 4VenturesBrandbot/1.0; +https://4ventures.es)',
Accept: 'text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.8',
},
signal: AbortSignal.timeout(15000),
});
if (!resp.ok) {
return NextResponse.json(
{ error: `Error al acceder a la URL: HTTP ${resp.status}` },
{ status: 400 }
);
}
const raw = await resp.text();
const clean = raw
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<noscript[\s\S]*?<\/noscript>/gi, '')
.replace(/<svg[\s\S]*?<\/svg>/gi, '')
.replace(/<\/(p|div|h[1-6]|li|tr)>/gi, '\n\n')
.replace(/<br\s*[\/]?>/gi, '\n');
text = clean
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'")
.replace(/&nbsp;/g, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/[ \t]+/g, ' ')
.replace(/\n\s*\n\s*\n+/g, '\n\n')
.trim();
try {
const parsed = new URL(url);
const pathSegments = parsed.pathname.split('/').filter(Boolean);
const lastSeg = pathSegments[pathSegments.length - 1];
filename = lastSeg
? lastSeg.endsWith('.html')
? lastSeg
: `${lastSeg}.txt`
: `${parsed.hostname}.txt`;
} catch {
filename = 'fuente-url.txt';
}
} catch (err) {
return NextResponse.json(
{
error: `No se pudo conectar a la URL: ${
err instanceof Error ? err.message : String(err)
}`,
},
{ status: 400 }
);
}
}
if (!text.trim()) {
return NextResponse.json({ error: 'Sin texto' }, { status: 400 });
}