Setting the file. One moment.
Recon · Event Prospecting · browserbase/skills · Skills Docs
ContentsBack to the top of the page scripts/recon.mjs
scripts/ recon.mjs
JavaScript · 125 lines · 4 KB
{ execFileSync }
from
'child_process'
;
16 import { writeFileSync, mkdirSync } from 'fs' ;
17 import { join } from 'path' ;
18
19 const args = process.argv. slice ( 2 );
20 if (args. length < 1 || args. includes ( '--help' )) {
21 console. error ( `Usage: node recon.mjs <event-url> [output-dir]` );
22 process. exit ( 1 );
23 }
24
25 const url = args[ 0 ];
26 const outDir = args[ 1 ];
27
28 function browse ( ... subargs ) {
29 return execFileSync ( 'browse' , subargs, {
30 encoding: 'utf-8' , maxBuffer: 8 * 1024 * 1024 , timeout: 60000 ,
31 });
32 }
33
34 function probe () {
35 // Navigate + settle
36 browse ( 'open' , url);
37 browse ( 'wait' , 'timeout' , '2500' );
38 const titleRes = JSON . parse ( browse ( 'get' , 'title' ));
39 const title = titleRes.title || '' ;
40
41 // Probe in priority order via a single eval — cheaper than N calls.
42 const probeJs = `(() => {
43 const nd = document.getElementById('__NEXT_DATA__');
44 const meta = document.querySelector('meta[name="generator"]');
45 const og = document.querySelector('meta[property="og:site_name"]');
46 const jsonLd = [...document.querySelectorAll('script[type="application/ld+json"]')]
47 .map(s => { try { return JSON.parse(s.textContent); } catch { return null; }})
48 .filter(Boolean);
49 return {
50 hasNextData: !!nd,
51 nextDataLen: nd ? nd.textContent.length : 0,
52 generator: meta ? meta.content : null,
53 ogSite: og ? og.content : null,
54 jsonLdEvents: jsonLd.filter(j => j['@type'] === 'Event').length,
55 hostname: location.hostname
56 };
57 })()` ;
58 const evalRes = JSON . parse ( browse ( 'eval' , probeJs));
59 const r = evalRes.result || {};
60
61 // Decide platform
62 let platform = 'custom' ;
63 let strategy = 'markdown' ;
64 let nextDataPaths = null ;
65
66 if (r.hasNextData) {
67 platform = 'next-data' ;
68 strategy = 'next-data-eval' ;
69 // Find arrays of speaker-like objects inside __NEXT_DATA__
70 const findJs = `(() => {
71 const data = JSON.parse(document.getElementById('__NEXT_DATA__').textContent);
72 const out = [];
73 function walk(o, path='') {
74 if (Array.isArray(o)) {
75 if (o.length > 3 && typeof o[0] === 'object' && o[0] !== null) {
76 const keys = Object.keys(o[0]);
77 const hasName = keys.some(k => /name/i.test(k));
78 const hasLinkedIn = JSON.stringify(o[0]).match(/linkedin/i);
79 if (hasName && hasLinkedIn) out.push({ path, len: o.length, keys: keys.slice(0,12) });
80 }
81 o.forEach((v,i) => walk(v, path+'['+i+']'));
82 } else if (o && typeof o === 'object') {
83 Object.keys(o).forEach(k => walk(o[k], path+'.'+k));
84 }
85 }
86 walk(data);
87 // Keep only top-level (non-nested) speaker arrays — drop talks[N].speakers
88 return out.filter(x => !/ \\ .talks \\ [ \\ d+ \\ ] \\ .speakers/.test(x.path)).slice(0, 5);
89 })()` ;
90 const findRes = JSON . parse ( browse ( 'eval' , findJs));
91 nextDataPaths = (findRes.result || []). map ( x => x.path);
92 } else if (r.generator && /sessionize/ i . test (r.generator)) {
93 platform = 'sessionize' ;
94 strategy = 'sessionize-api' ;
95 } else if (r.hostname && /lu \. ma/ . test (r.hostname)) {
96 platform = 'luma' ;
97 strategy = 'json-ld' ;
98 } else if (r.ogSite && /eventbrite/ i . test (r.ogSite)) {
99 platform = 'eventbrite' ;
100 strategy = 'json-ld' ;
101 } else if (r.jsonLdEvents > 0 ) {
102 platform = 'json-ld' ;
103 strategy = 'json-ld' ;
104 }
105
106 return {
107 url,
108 title,
109 platform,
110 strategy,
111 nextDataPaths,
112 signals: r,
113 probedAt: new Date (). toISOString (),
114 };
115 }
116
117 const result = probe ();
118
119 if (outDir) {
120 mkdirSync (outDir, { recursive: true });
121 const path = join (outDir, 'recon.json' );
122 writeFileSync (path, JSON . stringify (result, null , 2 ));
123 console. error ( `recon.json written → ${ path }` );
124 }
125 console. log ( JSON . stringify (result, null , 2 ));