/* =================================================================== Coletor dos posts editoriais do WordPress (raiz do dominio, separado do /brasiliana/ DSpace). Roda igual o coletor.js -- dentro da pagina, fetch() same-origin, grava em IndexedDB proprio ('bras_wp'). Nao baixa imagem nenhuma: as fotos dos posts ja apontam (em miniatura) pros mesmos handles que o acervo principal ja cobre. Só o texto e o mapeamento pra handles tem valor novo aqui. COMO USAR 1. Cole este arquivo na mesma aba (ou qualquer aba da mesma origem) 2. await WP.descobrir() -- enumera todos os posts (categorias x paginacao) 3. await WP.colher() -- busca o conteudo de cada post 4. await WP.status() 5. await WP.exportar() -- baixa wordpress-posts.jsonl =================================================================== */ (() => { const BASE = 'https://brasilianafotografica.bn.gov.br'; const CATS = ["25","26","311","5","9","35","189","1"]; const PAUSA = 1500; const sleep = ms => new Promise(r => setTimeout(r, ms)); const jit = () => PAUSA + Math.floor(Math.random() * 500); let db; const abrir = () => new Promise((res, rej) => { const q = indexedDB.open('bras_wp', 1); q.onupgradeneeded = e => { const d = e.target.result; if (!d.objectStoreNames.contains('fila')) d.createObjectStore('fila'); if (!d.objectStoreNames.contains('posts')) d.createObjectStore('posts'); }; q.onsuccess = e => res(e.target.result); q.onerror = () => rej(q.error); }); const put = (st, k, v) => new Promise((res, rej) => { const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k); t.oncomplete = res; t.onerror = () => rej(t.error); }); const putMuitos = (st, pares) => new Promise((res, rej) => { const t = db.transaction(st, 'readwrite'), o = t.objectStore(st); for (const [k, v] of pares) o.put(v, k); t.oncomplete = res; t.onerror = () => rej(t.error); }); const keys = st => new Promise((res, rej) => { const r = db.transaction(st).objectStore(st).getAllKeys(); r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); }); const getAll = st => new Promise((res, rej) => { const r = db.transaction(st).objectStore(st).getAll(); r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); }); const S = {fase: 'ocioso', atual: '', colhidos: 0, erros: 0, parar: false}; async function descobrir() { S.fase = 'descobrindo'; for (const cat of CATS) { if (S.parar) return; S.atual = `categoria ${cat}`; let pagina = 1, vazias = 0; while (vazias < 1 && pagina <= 100) { if (S.parar) return; const url = pagina === 1 ? `${BASE}/?cat=${cat}` : `${BASE}/?cat=${cat}&paged=${pagina}`; let html; try { const r = await fetch(url, {credentials:'same-origin'}); html = await r.text(); } catch(e) { vazias++; pagina++; await sleep(2000); continue; } const d = new DOMParser().parseFromString(html, 'text/html'); const ids = [...d.querySelectorAll('article')] .map(a => a.id?.match(/post-(\d+)/)?.[1]).filter(Boolean); if (!ids.length) { vazias++; pagina++; await sleep(300); continue; } await putMuitos('fila', ids.map(id => [id, cat])); pagina++; await sleep(jit()); } } const n = (await keys('fila')).length; console.log(`Descoberta: ${n} posts unicos`); } function limpaTexto(el) { const clone = el.cloneNode(true); clone.querySelectorAll('.sharify-container, script, style').forEach(e => e.remove()); return clone.textContent.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim(); } async function colherUm(id) { const r = await fetch(`${BASE}/?p=${id}`, {credentials: 'same-origin'}); if (!r.ok) throw new Error('HTTP ' + r.status); const html = await r.text(); const d = new DOMParser().parseFromString(html, 'text/html'); const titulo = d.querySelector('.entry-title')?.textContent?.trim() || d.title; const autor = d.querySelector('.author')?.textContent?.trim() || null; const dataIso = d.querySelector('time')?.getAttribute('datetime') || null; const categorias = [...new Set([...d.querySelectorAll('a[href*="?cat="]')].map(a => a.textContent.trim()).filter(Boolean))]; const contentEl = d.querySelector('.entry-content'); const corpo = contentEl ? limpaTexto(contentEl) : null; const handles = [...new Set([...html.matchAll(/handle\/(20\.500\.12156\.1)\/(\d+)/g)].map(m => `${m[1]}/${m[2]}`))]; return { id, url: `${BASE}/?p=${id}`, titulo, autor, data_iso: dataIso, categorias, corpo, handles_referenciados: handles, fetched_at: new Date().toISOString(), }; } async function colher() { S.fase = 'colhendo'; const fila = await keys('fila'), feitos = new Set(await keys('posts')); const pend = fila.filter(id => !feitos.has(id)); console.log(`${pend.length} posts a colher`); for (let i = 0; i < pend.length; i++) { if (S.parar) { S.fase = 'parado'; return; } const id = pend[i]; S.atual = `${i + 1}/${pend.length}`; try { const post = await colherUm(id); await put('posts', id, post); S.colhidos++; } catch (e) { await put('posts', id, {id, erro: String(e).slice(0, 80)}); S.erros++; } if (i % 25 === 0) console.log(` ${i}/${pend.length} ok=${S.colhidos} erro=${S.erros}`); await sleep(jit()); } S.fase = 'concluido'; console.log(`\nConcluido. ok=${S.colhidos} erro=${S.erros}`); } async function exportar() { const todos = await getAll('posts'); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([todos.map(o => JSON.stringify(o)).join('\n')], {type: 'application/x-ndjson'})); a.download = 'wordpress-posts.jsonl'; document.body.appendChild(a); a.click(); a.remove(); console.log(`exportados ${todos.length} posts`); return todos.length; } window.WP = { descobrir, colher, exportar, status: async () => ({...S, naFila: (await keys('fila')).length, noBanco: (await keys('posts')).length}), parar: () => { S.parar = true; }, }; (async () => { db = await abrir(); console.log('WP pronto. await WP.descobrir() depois await WP.colher()'); })(); })();