primocode 8.38.1 → 8.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/act.js +87 -2
- package/lib/audio-livre.js +172 -0
- package/lib/catalogo.js +7 -0
- package/lib/marca.js +280 -0
- package/lib/referencia.js +210 -63
- package/lib/remotion.js +245 -45
- package/lib/studio.js +76 -3
- package/lib/tools.js +13 -0
- package/package.json +2 -2
- package/studio/__pycache__/edge.cpython-311.pyc +0 -0
- package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
- package/studio/arquivos/tela-motion-3ecaa.json +135 -0
- package/studio/arquivos/tela-motion-d6150.json +135 -0
- package/studio/edge.py +66 -16
- package/studio/ferramentas/01-modelos.md +20 -2
- package/studio/ferramentas/corte.md +140 -35
- package/studio/ferramentas/tela.md +21 -0
- package/studio/server.py +47 -11
- package/studio/studio.log +28 -0
- package/studio/web/js/player.js +30 -2
- package/studio/web/js/render.js +74 -4
- package/studio/web/js/sfx.js +128 -2
- package/studio/web/render.css +95 -0
package/lib/referencia.js
CHANGED
|
@@ -56,7 +56,7 @@ function hostBloqueado(hostname) {
|
|
|
56
56
|
return false; // hostname comum (DNS) — não dá pra saber o IP sem resolver
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const TIMEOUT_MS =
|
|
59
|
+
const TIMEOUT_MS = 8000; // por página; os buscadores têm o PRAZO_BUSCADOR, menor
|
|
60
60
|
const MAX_POR_FONTE = 4;
|
|
61
61
|
const MAX_TEXTO = 4500;
|
|
62
62
|
// Quanto HTML baixar antes de parar. Só os primeiros MAX_TEXTO caracteres de
|
|
@@ -95,7 +95,12 @@ function viaProxy(alvo, { timeout }) {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
// Bytes, não texto: a logo em PNG, a música em MP3. Mesmo caminho (proxy,
|
|
99
|
+
// redirect, prazo duro, SSRF), corpo em Buffer e teto maior — um mp3 de três
|
|
100
|
+
// minutos tem 5 MB, e 400 KB o cortariam no primeiro refrão.
|
|
101
|
+
const LIMITE_BYTES = 40 * 1024 * 1024;
|
|
102
|
+
|
|
103
|
+
function pegar(url, { timeout = TIMEOUT_MS, saltos = 0, binario = false } = {}) {
|
|
99
104
|
return new Promise((resolve) => {
|
|
100
105
|
let req;
|
|
101
106
|
const alvo = (() => { try { return new URL(url); } catch { return null; } })();
|
|
@@ -128,6 +133,22 @@ function pegar(url, { timeout = TIMEOUT_MS, saltos = 0 } = {}) {
|
|
|
128
133
|
})).catch(() => null)
|
|
129
134
|
: Promise.resolve(opcoes);
|
|
130
135
|
|
|
136
|
+
// PRAZO DURO, do começo ao fim. O `timeout` do http é de OCIOSIDADE:
|
|
137
|
+
// um servidor que pinga um byte a cada segundo, ou um túnel que abre e
|
|
138
|
+
// trava no aperto de mão TLS, passava dele sem nunca disparar — medido:
|
|
139
|
+
// api.duckduckgo.com levava 11 s para devolver nada com timeout de 6 s.
|
|
140
|
+
// Aqui o relógio conta o tempo total; estourou, entrega o que veio.
|
|
141
|
+
let corpoParcial = '';
|
|
142
|
+
let encerrado = false;
|
|
143
|
+
const resolver = resolve;
|
|
144
|
+
const relogio = setTimeout(() => {
|
|
145
|
+
if (encerrado) return;
|
|
146
|
+
encerrado = true;
|
|
147
|
+
try { if (req) req.destroy(); } catch { /* já morto */ }
|
|
148
|
+
resolver(corpoParcial.length > 2000 ? corpoParcial : null);
|
|
149
|
+
}, timeout + 500);
|
|
150
|
+
resolve = (v) => { clearTimeout(relogio); if (!encerrado) { encerrado = true; resolver(v); } };
|
|
151
|
+
|
|
131
152
|
abrir.then((cfg) => {
|
|
132
153
|
if (!cfg) return resolve(null);
|
|
133
154
|
try {
|
|
@@ -136,15 +157,29 @@ function pegar(url, { timeout = TIMEOUT_MS, saltos = 0 } = {}) {
|
|
|
136
157
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
137
158
|
res.resume();
|
|
138
159
|
const destino = new URL(res.headers.location, url).href;
|
|
139
|
-
return pegar(destino, { timeout, saltos: saltos + 1 }).then(resolve);
|
|
160
|
+
return pegar(destino, { timeout, saltos: saltos + 1, binario }).then(resolve);
|
|
140
161
|
}
|
|
141
162
|
if (res.statusCode !== 200) { res.resume(); return resolve(null); }
|
|
163
|
+
if (binario) {
|
|
164
|
+
// Bytes inteiros ou nada: meia logo e meio mp3 não servem.
|
|
165
|
+
const pedacos = [];
|
|
166
|
+
let total = 0;
|
|
167
|
+
res.on('data', (c) => {
|
|
168
|
+
total += c.length;
|
|
169
|
+
if (total > LIMITE_BYTES) { res.destroy(); return resolve(null); }
|
|
170
|
+
pedacos.push(c);
|
|
171
|
+
});
|
|
172
|
+
res.on('end', () => resolve(Buffer.concat(pedacos)));
|
|
173
|
+
res.on('error', () => resolve(null));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
142
176
|
let corpo = '';
|
|
143
177
|
let pronto = false;
|
|
144
178
|
const entregar = (valor) => { if (!pronto) { pronto = true; resolve(valor); } };
|
|
145
179
|
res.setEncoding('utf8');
|
|
146
180
|
res.on('data', (c) => {
|
|
147
181
|
corpo += c;
|
|
182
|
+
corpoParcial = corpo;
|
|
148
183
|
// Página grande: PARA de baixar e entrega o que já veio.
|
|
149
184
|
//
|
|
150
185
|
// Antes isto era `req.destroy()` sem entregar nada, e o
|
|
@@ -338,7 +373,7 @@ async function chutesVivos(chutes, limite = 2) {
|
|
|
338
373
|
// centenas de KB. Com 6 s o site CERTO chegava atrasado, era descartado
|
|
339
374
|
// como morto, e a pesquisa falhava com o site no ar — medido.
|
|
340
375
|
const respostas = await Promise.all(chutes.slice(0, 12).map(async (c) => {
|
|
341
|
-
const html = await pegar(c.url, { timeout:
|
|
376
|
+
const html = await pegar(c.url, { timeout: 8000 });
|
|
342
377
|
return html && html.length > 500 ? { cand: c, html } : null;
|
|
343
378
|
}));
|
|
344
379
|
const vivos = [];
|
|
@@ -373,9 +408,15 @@ function limparLinks(pares, limite) {
|
|
|
373
408
|
return bons;
|
|
374
409
|
}
|
|
375
410
|
|
|
411
|
+
// Prazo de UM buscador. Eles rodam em paralelo (ver `buscar`), então este é
|
|
412
|
+
// o teto da busca inteira — não a soma. Medido: o DuckDuckGo estava levando
|
|
413
|
+
// 11 s para devolver NADA, e a pesquisa esperava por ele antes de tentar o
|
|
414
|
+
// Bing, que respondia em meio segundo.
|
|
415
|
+
const PRAZO_BUSCADOR = 6000;
|
|
416
|
+
|
|
376
417
|
/** DuckDuckGo (HTML). O destino vem embrulhado em /l/?uddg=<url>. */
|
|
377
418
|
async function buscarDDG(consulta, limite) {
|
|
378
|
-
const html = await pegar(`https://duckduckgo.com/html/?q=${encodeURIComponent(consulta)}
|
|
419
|
+
const html = await pegar(`https://duckduckgo.com/html/?q=${encodeURIComponent(consulta)}`, { timeout: PRAZO_BUSCADOR });
|
|
379
420
|
if (!html) return [];
|
|
380
421
|
const pares = [];
|
|
381
422
|
const re = /<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
@@ -442,29 +483,73 @@ async function buscarRespostaDDG(consulta, limite) {
|
|
|
442
483
|
return limparLinks(achados, limite);
|
|
443
484
|
}
|
|
444
485
|
|
|
445
|
-
/**
|
|
486
|
+
/**
|
|
487
|
+
* O destino de um link do Bing.
|
|
488
|
+
*
|
|
489
|
+
* O Bing embrulha TODO resultado em `bing.com/ck/a?…&u=a1<base64url>`: o
|
|
490
|
+
* `a1` é prefixo, o resto é a URL em base64 (alfabeto de URL, sem "="). O
|
|
491
|
+
* parser antigo lia o `<cite>` — que mostra a URL com " › " no lugar das
|
|
492
|
+
* barras e reticências no meio — e por isso casava zero resultados numa
|
|
493
|
+
* página com dez. Medido no IP deste servidor: 117 KB de HTML, dez
|
|
494
|
+
* resultados, nenhum aproveitado. O buscador respondia; nós é que não líamos.
|
|
495
|
+
*/
|
|
496
|
+
function destinoBing(href) {
|
|
497
|
+
const h = String(href || '').replace(/&/g, '&');
|
|
498
|
+
const m = /[?&]u=a1([A-Za-z0-9_-]+)/.exec(h);
|
|
499
|
+
if (!m) return /^https?:\/\//i.test(h) && !/bing\.com\//i.test(h) ? h : null;
|
|
500
|
+
try {
|
|
501
|
+
const b64 = m[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
502
|
+
const url = Buffer.from(b64 + '='.repeat((4 - (b64.length % 4)) % 4), 'base64').toString('utf8');
|
|
503
|
+
return /^https?:\/\//i.test(url) ? url : null; // "/images/search?…" é do próprio Bing
|
|
504
|
+
} catch { return null; }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Bing (HTML). Responde rápido e sem bloquear onde o DDG cala. */
|
|
446
508
|
async function buscarBing(consulta, limite) {
|
|
447
|
-
const html = await pegar(`https://www.bing.com/search?q=${encodeURIComponent(consulta)}&setlang=en
|
|
509
|
+
const html = await pegar(`https://www.bing.com/search?q=${encodeURIComponent(consulta)}&setlang=en`, { timeout: PRAZO_BUSCADOR });
|
|
448
510
|
if (!html) return [];
|
|
449
511
|
const pares = [];
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const mostrado = textoDe(bruto);
|
|
456
|
-
if (!/^https?:\/\//i.test(mostrado) || /[…]/.test(mostrado)) continue;
|
|
457
|
-
pares.push({ titulo: mostrado.replace(/\s*›\s*/g, ' / '), url: mostrado.replace(/\s*›\s*/g, '/') });
|
|
512
|
+
const re = /<h2[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
513
|
+
let m;
|
|
514
|
+
while ((m = re.exec(html))) {
|
|
515
|
+
const url = destinoBing(m[1]);
|
|
516
|
+
if (url) pares.push({ titulo: textoDe(m[2]), url });
|
|
458
517
|
}
|
|
459
|
-
|
|
518
|
+
return limparLinks(pares, limite);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Yahoo (HTML). Também embrulha: `r.search.yahoo.com/…/RU=<url codificada>/RK=…`.
|
|
523
|
+
* É a segunda porta que responde onde o DDG não responde — e os índices dos
|
|
524
|
+
* dois (Bing e Yahoo) são parecidos, então os dois juntos confirmam um ao
|
|
525
|
+
* outro mais do que ampliam.
|
|
526
|
+
*/
|
|
527
|
+
function destinoYahoo(href) {
|
|
528
|
+
const h = String(href || '').replace(/&/g, '&');
|
|
529
|
+
const m = /\/RU=([^/]+)/.exec(h);
|
|
530
|
+
if (!m) return /^https?:\/\//i.test(h) && !/yahoo\.com\//i.test(h) ? h : null;
|
|
531
|
+
try {
|
|
532
|
+
const url = decodeURIComponent(m[1]);
|
|
533
|
+
return /^https?:\/\//i.test(url) && !/yahoo\.com\//i.test(url) ? url : null;
|
|
534
|
+
} catch { return null; }
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function buscarYahoo(consulta, limite) {
|
|
538
|
+
const html = await pegar(`https://search.yahoo.com/search?p=${encodeURIComponent(consulta)}`, { timeout: PRAZO_BUSCADOR });
|
|
539
|
+
if (!html) return [];
|
|
540
|
+
const pares = [];
|
|
541
|
+
const re = /<h3[^>]*class="[^"]*\btitle\b[^"]*"[^>]*>\s*<a[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
460
542
|
let m;
|
|
461
|
-
while ((m = re.exec(html)))
|
|
543
|
+
while ((m = re.exec(html))) {
|
|
544
|
+
const url = destinoYahoo(m[1]);
|
|
545
|
+
if (url) pares.push({ titulo: textoDe(m[2]), url });
|
|
546
|
+
}
|
|
462
547
|
return limparLinks(pares, limite);
|
|
463
548
|
}
|
|
464
549
|
|
|
465
550
|
/** SearXNG público, em JSON — a terceira porta. */
|
|
466
551
|
async function buscarSearx(consulta, limite) {
|
|
467
|
-
const corpo = await pegar(`https://searx.be/search?q=${encodeURIComponent(consulta)}&format=json
|
|
552
|
+
const corpo = await pegar(`https://searx.be/search?q=${encodeURIComponent(consulta)}&format=json`, { timeout: PRAZO_BUSCADOR });
|
|
468
553
|
if (!corpo) return [];
|
|
469
554
|
try {
|
|
470
555
|
const j = JSON.parse(corpo);
|
|
@@ -473,29 +558,33 @@ async function buscarSearx(consulta, limite) {
|
|
|
473
558
|
}
|
|
474
559
|
|
|
475
560
|
/**
|
|
476
|
-
* Busca na web
|
|
477
|
-
*
|
|
478
|
-
*
|
|
561
|
+
* Busca na web — TODAS as portas de uma vez, e fica com o que voltar.
|
|
562
|
+
*
|
|
563
|
+
* Em fila, o buscador que não responde segura os outros: o DDG gastava 11 s
|
|
564
|
+
* para calar, e só então o Bing era tentado. Em paralelo, a pesquisa leva o
|
|
565
|
+
* tempo do MAIS RÁPIDO que respondeu com algo (e no máximo PRAZO_BUSCADOR).
|
|
566
|
+
* A ordem da lista é a ordem de confiança na hora de juntar: o que vem
|
|
567
|
+
* primeiro entra primeiro. Sem chave de API em nenhuma delas.
|
|
479
568
|
*/
|
|
480
569
|
async function buscar(consulta, limite = MAX_POR_FONTE) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// fila acabava no Searx e, com os três calados, a pesquisa inteira voltava
|
|
484
|
-
// vazia — mesmo havendo resposta boa na Wikipédia.
|
|
485
|
-
const portas = [buscarDDG, buscarBing, buscarSearx, buscarRespostaDDG, buscarWikipedia];
|
|
570
|
+
const portas = [buscarBing, buscarYahoo, buscarDDG, buscarSearx, buscarRespostaDDG, buscarWikipedia];
|
|
571
|
+
const respostas = await Promise.all(portas.map((p) => p(consulta, limite).catch(() => [])));
|
|
486
572
|
const juntos = [];
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
573
|
+
const dominios = new Map();
|
|
574
|
+
for (const achados of respostas) {
|
|
575
|
+
for (const a of achados) {
|
|
576
|
+
if (juntos.some((j) => j.url === a.url)) continue;
|
|
577
|
+
// No máximo dois links do mesmo domínio: uma página de resultados
|
|
578
|
+
// dominada por um site só (a Wikipédia, o próprio produto) não é
|
|
579
|
+
// variedade de fonte.
|
|
580
|
+
const dom = (a.url.match(/^https?:\/\/(?:www\.)?([^/]+)/i) || [])[1] || a.url;
|
|
581
|
+
const n = dominios.get(dom) || 0;
|
|
582
|
+
if (n >= 2) continue;
|
|
583
|
+
dominios.set(dom, n + 1);
|
|
584
|
+
juntos.push(a);
|
|
585
|
+
}
|
|
497
586
|
}
|
|
498
|
-
return juntos.slice(0, limite);
|
|
587
|
+
return juntos.slice(0, limite * 2);
|
|
499
588
|
}
|
|
500
589
|
|
|
501
590
|
/** Respostas do StackOverflow — a API pública, sem chave. */
|
|
@@ -515,24 +604,61 @@ async function stackoverflow(consulta, limite = 3) {
|
|
|
515
604
|
} catch { return []; }
|
|
516
605
|
}
|
|
517
606
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
607
|
+
/**
|
|
608
|
+
* A documentação oficial, direto — não depende de buscador nenhum, e é o que
|
|
609
|
+
* salva a pesquisa em rede corporativa que bloqueia buscador e libera site de
|
|
610
|
+
* documentação.
|
|
611
|
+
*
|
|
612
|
+
* A MDN entra pela API dela, não pela página de busca: a página é uma casca
|
|
613
|
+
* que carrega os resultados por JavaScript, e o que chegava aqui eram 226
|
|
614
|
+
* caracteres de menu. A pesquisa dizia "1 fonte lida" e o modelo recebia
|
|
615
|
+
* nada — foi assim que "liquid glass css" voltou de mãos vazias com a
|
|
616
|
+
* resposta certa (backdrop-filter) a uma chamada de distância. A API devolve
|
|
617
|
+
* os artigos com resumo, e o artigo é a melhor fonte que existe sobre CSS.
|
|
618
|
+
* O mesmo vale para o python.org: a busca lá é JavaScript, e o índice dos
|
|
619
|
+
* módulos é o que se lê.
|
|
620
|
+
*/
|
|
621
|
+
// Palavras que toda consulta técnica carrega e que não apontam artigo nenhum.
|
|
622
|
+
// "effect" casava vector-effect para "liquid glass css effect" — e o modelo
|
|
623
|
+
// recebia a doc de SVG como referência de vidro.
|
|
624
|
+
const TECNICAS_GENERICAS = new Set(['css', 'html', 'javascript', 'js', 'effect', 'efeito', 'effects', 'como', 'fazer',
|
|
625
|
+
'how', 'make', 'create', 'criar', 'tutorial', 'example', 'exemplo', 'code', 'codigo', 'with', 'using', 'web',
|
|
626
|
+
'style', 'estilo', 'property', 'propriedade', 'usar', 'use', 'best', 'melhor', 'simple', 'simples']);
|
|
627
|
+
|
|
628
|
+
async function buscarMDN(consulta, limite = 3) {
|
|
629
|
+
const palavras = palavrasDaConsulta(consulta).filter((p) => !TECNICAS_GENERICAS.has(p));
|
|
630
|
+
if (!palavras.length) return [];
|
|
631
|
+
const bruto = await pegar(`https://developer.mozilla.org/api/v1/search?q=${encodeURIComponent(palavras.join(' '))}&locale=en-US`, { timeout: PRAZO_BUSCADOR });
|
|
632
|
+
if (!bruto) return [];
|
|
633
|
+
try {
|
|
634
|
+
const j = JSON.parse(bruto);
|
|
635
|
+
return (j.documents || [])
|
|
636
|
+
// O índice da MDN casa por palavra, e "effect" casa vector-effect.
|
|
637
|
+
// Só entra o artigo cujo título ou caminho traz uma palavra que
|
|
638
|
+
// é DO ASSUNTO — as genéricas já saíram acima.
|
|
639
|
+
.filter((d) => {
|
|
640
|
+
const alvo = `${d.title} ${d.slug}`.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
641
|
+
return palavras.some((p) => alvo.includes(p));
|
|
642
|
+
})
|
|
643
|
+
.slice(0, limite).map((d) => ({
|
|
644
|
+
titulo: d.title,
|
|
645
|
+
url: 'https://developer.mozilla.org' + d.mdn_url,
|
|
646
|
+
nota: 'MDN' + (d.summary ? ' — ' + String(d.summary).slice(0, 160) : ''),
|
|
647
|
+
}));
|
|
648
|
+
} catch { return []; }
|
|
649
|
+
}
|
|
650
|
+
|
|
521
651
|
const DOCS = [
|
|
522
|
-
[/\b(js|javascript|array|string|promise|fetch|dom|css|html|regex)\b/i,
|
|
523
|
-
|
|
524
|
-
[/\b(
|
|
525
|
-
[/\b(
|
|
526
|
-
[/\b(python|django|flask|pandas)\b/i,
|
|
527
|
-
(q) => `https://docs.python.org/3/search.html?q=${encodeURIComponent(q)}`],
|
|
652
|
+
[/\b(js|javascript|array|string|promise|fetch|dom|css|html|regex|svg|canvas|webgl|animation|keyframes|backdrop|filter|grid|flex)\b/i, buscarMDN],
|
|
653
|
+
[/\b(react|jsx|hook|usestate|useeffect)\b/i, async () => [{ titulo: 'React — referência', url: 'https://react.dev/reference/react', nota: 'documentação oficial' }]],
|
|
654
|
+
[/\b(node|npm|express|fs|require)\b/i, async () => [{ titulo: 'Node.js — API', url: 'https://nodejs.org/docs/latest/api/', nota: 'documentação oficial' }]],
|
|
655
|
+
[/\b(python|django|flask|pandas)\b/i, async () => [{ titulo: 'Python — índice dos módulos', url: 'https://docs.python.org/3/py-modindex.html', nota: 'documentação oficial' }]],
|
|
528
656
|
];
|
|
529
657
|
|
|
530
|
-
function docsConhecidas(consulta) {
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
return achados;
|
|
658
|
+
async function docsConhecidas(consulta) {
|
|
659
|
+
const casam = DOCS.filter(([re]) => re.test(consulta));
|
|
660
|
+
const listas = await Promise.all(casam.map(([, f]) => f(consulta).catch(() => [])));
|
|
661
|
+
return listas.flat();
|
|
536
662
|
}
|
|
537
663
|
|
|
538
664
|
/**
|
|
@@ -543,9 +669,15 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
543
669
|
const termo = String(consulta || '').trim();
|
|
544
670
|
if (!termo) return { ok: false, error: 'diga o que pesquisar' };
|
|
545
671
|
|
|
546
|
-
|
|
672
|
+
// Tudo de uma vez: os buscadores, o StackOverflow, a documentação e a
|
|
673
|
+
// sonda dos sites prováveis da empresa. A pesquisa leva o tempo do mais
|
|
674
|
+
// lento que RESPONDEU, não a soma de todos.
|
|
675
|
+
const palpites = sitesProvaveis(termo);
|
|
676
|
+
const [web, so, docs, sondados] = await Promise.all([
|
|
547
677
|
buscar(termo, MAX_POR_FONTE),
|
|
548
678
|
stackoverflow(termo, 2),
|
|
679
|
+
docsConhecidas(termo),
|
|
680
|
+
chutesVivos(palpites),
|
|
549
681
|
]);
|
|
550
682
|
|
|
551
683
|
// A documentação oficial entra SEMPRE na fila, não só quando as outras
|
|
@@ -559,20 +691,28 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
559
691
|
// respondem ocupam vaga. São dez palpites (5 TLDs x com/sem www) e a
|
|
560
692
|
// maioria não existe — enfileirar todos expulsaria os resultados reais
|
|
561
693
|
// do buscador. A sonda roda em paralelo e o HTML já volta aproveitado.
|
|
562
|
-
const
|
|
563
|
-
const { vivos, html: htmlChutes } = await chutesVivos(palpites);
|
|
694
|
+
const { vivos, html: htmlChutes } = sondados;
|
|
564
695
|
// A sonda ORDENA, não faz porteiro. Ela é uma corrida contra o relógio
|
|
565
696
|
// contra home pesada e rede lenta, e perdê-la não pode significar perder
|
|
566
697
|
// o site certo: quando nada responde a tempo, os primeiros palpites vão
|
|
567
698
|
// para a fila mesmo assim e o laço de leitura tenta de novo, sem pressa.
|
|
568
699
|
const chutes = vivos.length ? vivos : palpites.slice(0, 3);
|
|
569
700
|
const palavras = palavrasDaConsulta(termo);
|
|
570
|
-
|
|
701
|
+
// A documentação oficial vai NA FRENTE dos links de buscador: quando a
|
|
702
|
+
// consulta é técnica, o artigo da MDN vale mais que o blog que o Bing
|
|
703
|
+
// pôs em primeiro.
|
|
704
|
+
const brutos = [...chutes, ...docs, ...web, ...so];
|
|
571
705
|
|
|
572
706
|
// Fora o que não é sobre o assunto. Sem isto o modelo recebia "Milan" e
|
|
573
707
|
// "History of autism" para uma pesquisa sobre uma empresa, e escrevia o
|
|
574
708
|
// vídeo com aquilo.
|
|
575
|
-
|
|
709
|
+
// A documentação oficial passa pelo nome: "backdrop-filter CSS property"
|
|
710
|
+
// não repete as palavras de "liquid glass css effect", e mesmo assim é a
|
|
711
|
+
// resposta. Quem a escolheu foi o índice da MDN, que entende sinônimo —
|
|
712
|
+
// o filtro de palavras é para link de buscador, que devolve página
|
|
713
|
+
// aleatória quando não conhece o assunto.
|
|
714
|
+
const filtrados = brutos.filter((cand) => cand.nota === 'site provável da empresa'
|
|
715
|
+
|| /^(MDN|documentação oficial)/.test(String(cand.nota || '')) || relevante(cand, palavras));
|
|
576
716
|
const candidatos = filtrados.slice(0, Math.max(2, Math.min(10, paginas + 6)));
|
|
577
717
|
const descartados = brutos.length - filtrados.length;
|
|
578
718
|
if (!candidatos.length) {
|
|
@@ -602,14 +742,20 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
602
742
|
// Os chutes de domínio ficam de lado até o fim: só então dá para saber
|
|
603
743
|
// qual deles fala mais do assunto.
|
|
604
744
|
const chutesLidos = [];
|
|
605
|
-
|
|
745
|
+
// As páginas abrem JUNTAS, não uma depois da outra: dez candidatos a 2 s
|
|
746
|
+
// cada eram 20 s de espera antes de ler a primeira linha. Quem não abriu
|
|
747
|
+
// vira null e é pulado na ordem — a ordem de confiança continua valendo.
|
|
748
|
+
const paginasAbertas = await Promise.all(candidatos.map((cand) =>
|
|
749
|
+
htmlChutes.has(cand.url) ? Promise.resolve(htmlChutes.get(cand.url)) : pegar(cand.url)));
|
|
750
|
+
for (let ic = 0; ic < candidatos.length; ic++) {
|
|
751
|
+
const cand = candidatos[ic];
|
|
606
752
|
if (lidas.length >= paginas) break;
|
|
607
|
-
|
|
608
|
-
// a mesma página duas vezes.
|
|
609
|
-
const html = htmlChutes.get(cand.url) || await pegar(cand.url);
|
|
753
|
+
const html = paginasAbertas[ic];
|
|
610
754
|
if (!html) continue;
|
|
611
755
|
const texto = textoDe(html);
|
|
612
|
-
|
|
756
|
+
// 400, não 200: uma casca de página que carrega tudo por JavaScript
|
|
757
|
+
// deixa 200-300 caracteres de menu, e isso passava como "fonte lida".
|
|
758
|
+
if (texto.length < 400) continue;
|
|
613
759
|
if (pareceCodigo(texto)) continue;
|
|
614
760
|
// O chute de domínio precisa PROVAR que acertou. "Paris Group" tem
|
|
615
761
|
// parisgroup.com (um grupo de varejo no exterior) e parisgroup.ai (a
|
|
@@ -675,4 +821,5 @@ async function pesquisar({ consulta, paginas = 3 }) {
|
|
|
675
821
|
}
|
|
676
822
|
|
|
677
823
|
module.exports = { pesquisar, buscar, stackoverflow, textoDe, buscarWikipedia, buscarRespostaDDG, pegar,
|
|
678
|
-
relevante, quantoBate, palavrasDaConsulta, sitesProvaveis, pareceCodigo
|
|
824
|
+
relevante, quantoBate, palavrasDaConsulta, sitesProvaveis, chutesVivos, pareceCodigo,
|
|
825
|
+
destinoBing, destinoYahoo, buscarBing, buscarYahoo, buscarMDN, docsConhecidas, PRAZO_BUSCADOR };
|