clawd/scripts/browser.js

233 lines
7.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Fast browser automation via Puppeteer
*
* Usage: node browser.js <command> [args]
*
* Commands:
* tabs - List open tabs
* goto <url> - Navigate to URL
* screenshot [url] [output] - Screenshot (current page or URL)
* text [url] - Get page text
* html [selector] - Get HTML
* click <selector> - Click element
* type <selector> <text> - Type into field
* eval <js> - Run JavaScript
* close [index] - Close tab
* amazon <subcmd> - Amazon: cart, search <q>, buy-again
* x <subcmd> - X/Twitter: feed, search <q>, user <handle>
* tv <symbol> - TradingView quote
*/
const puppeteer = require('puppeteer-core');
const fs = require('fs');
const BROWSER_URL = 'http://127.0.0.1:9222';
async function connect() {
return puppeteer.connect({ browserURL: BROWSER_URL, defaultViewport: null });
}
async function getPage(browser, urlFilter) {
const pages = await browser.pages();
if (urlFilter) {
return pages.find(p => p.url().includes(urlFilter)) || pages[0];
}
return pages[0];
}
const commands = {
async tabs(browser) {
const pages = await browser.pages();
pages.forEach((p, i) => console.log(`[${i}] ${p.url()}`));
},
async goto(browser, [url]) {
if (!url) throw new Error('Need URL');
const page = (await browser.pages())[0] || await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
console.log(`${page.url()}`);
},
async screenshot(browser, [url, output = '/tmp/screenshot.png']) {
const page = url && url !== '-'
? await (async () => { const p = await browser.newPage(); await p.goto(url, {waitUntil: 'networkidle2'}); return p; })()
: (await browser.pages())[0];
await page.screenshot({ path: output });
console.log(`📸 ${output}`);
},
async text(browser, [url]) {
let page = (await browser.pages())[0];
if (url) {
page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
}
const text = await page.evaluate(() => document.body.innerText);
console.log(text.substring(0, 10000));
},
async html(browser, [selector]) {
const page = (await browser.pages())[0];
const html = await page.evaluate(s => {
const el = s ? document.querySelector(s) : document.body;
return el ? el.innerHTML : null;
}, selector);
console.log(html?.substring(0, 10000) || 'Not found');
},
async click(browser, [selector]) {
const page = (await browser.pages())[0];
await page.click(selector);
console.log(`✓ Clicked ${selector}`);
},
async type(browser, [selector, ...text]) {
const page = (await browser.pages())[0];
await page.type(selector, text.join(' '));
console.log(`✓ Typed into ${selector}`);
},
async eval(browser, args) {
const page = (await browser.pages())[0];
const result = await page.evaluate(args.join(' '));
console.log(JSON.stringify(result, null, 2));
},
async close(browser, [index = 0]) {
const pages = await browser.pages();
if (pages[+index]) {
await pages[+index].close();
console.log(`Closed tab ${index}`);
}
},
// Amazon shortcuts
async amazon(browser, [subcmd, ...args]) {
let page = await getPage(browser, 'amazon.com');
if (!page?.url().includes('amazon')) {
page = await browser.newPage();
}
switch (subcmd) {
case 'cart':
await page.goto('https://www.amazon.com/gp/cart/view.html', { waitUntil: 'networkidle2' });
const cartItems = await page.evaluate(() => {
const items = document.querySelectorAll('.sc-list-item-content');
return Array.from(items).map(item => {
const title = item.querySelector('.sc-product-title')?.textContent?.trim();
const price = item.querySelector('.sc-product-price')?.textContent?.trim();
return { title, price };
}).filter(i => i.title);
});
console.log('🛒 Cart:');
cartItems.forEach(i => console.log(` - ${i.title} (${i.price})`));
if (!cartItems.length) console.log(' (empty)');
break;
case 'search':
const q = args.join(' ');
await page.goto(`https://www.amazon.com/s?k=${encodeURIComponent(q)}`, { waitUntil: 'networkidle2' });
console.log(`🔍 Amazon search: ${q}`);
break;
case 'buy-again':
await page.goto('https://www.amazon.com/gp/buyagain', { waitUntil: 'networkidle2' });
console.log('📦 Buy Again page loaded');
break;
default:
console.log('Amazon subcmds: cart, search <query>, buy-again');
}
},
// X/Twitter shortcuts
async x(browser, [subcmd, ...args]) {
let page = await getPage(browser, 'x.com');
if (!page?.url().includes('x.com')) {
page = await browser.newPage();
}
switch (subcmd) {
case 'feed':
await page.goto('https://x.com/home', { waitUntil: 'networkidle2' });
await page.screenshot({ path: '/tmp/x-feed.png' });
console.log('📸 /tmp/x-feed.png');
break;
case 'search':
const q = args.join(' ');
await page.goto(`https://x.com/search?q=${encodeURIComponent(q)}&f=live`, { waitUntil: 'networkidle2' });
console.log(`🔍 X search: ${q}`);
await page.screenshot({ path: '/tmp/x-search.png' });
console.log('📸 /tmp/x-search.png');
break;
case 'user':
const handle = args[0]?.replace('@', '');
await page.goto(`https://x.com/${handle}`, { waitUntil: 'networkidle2' });
console.log(`👤 @${handle}`);
await page.screenshot({ path: `/tmp/x-${handle}.png` });
console.log(`📸 /tmp/x-${handle}.png`);
break;
default:
console.log('X subcmds: feed, search <query>, user <handle>');
}
},
// TradingView quote
async tv(browser, [symbol]) {
if (!symbol) throw new Error('Need symbol');
let page = await getPage(browser, 'tradingview');
if (!page?.url().includes('tradingview')) {
page = await browser.newPage();
}
await page.goto(`https://www.tradingview.com/symbols/${symbol.toUpperCase()}/`, { waitUntil: 'networkidle2' });
const data = await page.evaluate(() => {
const price = document.querySelector('[data-symbol-last-value]')?.textContent;
const change = document.querySelector('[data-symbol-change]')?.textContent;
return { price, change };
});
console.log(`📈 ${symbol.toUpperCase()}: ${data.price || 'N/A'} (${data.change || 'N/A'})`);
}
};
async function main() {
const [,, cmd, ...args] = process.argv;
if (!cmd || cmd === 'help') {
console.log(`Browser automation commands:
tabs - List open tabs
goto <url> - Navigate
screenshot [url] [out] - Screenshot
text [url] - Get page text
click <selector> - Click
type <sel> <text> - Type
eval <js> - Run JS
amazon cart|search|buy-again
x feed|search|user
tv <symbol> - Stock quote`);
return;
}
if (!commands[cmd]) {
console.error(`Unknown: ${cmd}`);
process.exit(1);
}
const browser = await connect();
try {
await commands[cmd](browser, args);
} finally {
browser.disconnect();
}
}
main().catch(e => {
console.error('Error:', e.message);
process.exit(1);
});