- Add WorkerFingerprint interface with timezone, city, state, ip, locale - Store fingerprint in TaskWorker after preflight passes - Pass fingerprint through TaskContext to handlers - Apply timezone via CDP and locale via Accept-Language header - Ensures browser fingerprint matches proxy IP location This fixes anti-detect detection where timezone/locale mismatch with proxy IP was getting blocked by Cloudflare. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
248 lines
8.0 KiB
TypeScript
248 lines
8.0 KiB
TypeScript
/**
|
|
* Explore Treez site structure to find full product catalog
|
|
*
|
|
* Usage: npx ts-node scripts/explore-treez-structure.ts
|
|
*/
|
|
|
|
import puppeteer from 'puppeteer';
|
|
|
|
const STORE_ID = 'best';
|
|
|
|
async function sleep(ms: number): Promise<void> {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function main() {
|
|
console.log('='.repeat(60));
|
|
console.log('Exploring Treez Site Structure');
|
|
console.log('='.repeat(60));
|
|
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1920, height: 1080 });
|
|
|
|
try {
|
|
// Navigate to base menu URL
|
|
const baseUrl = `https://${STORE_ID}.treez.io/onlinemenu/?customerType=ADULT`;
|
|
console.log(`\n[1] Navigating to: ${baseUrl}`);
|
|
await page.goto(baseUrl, { waitUntil: 'networkidle2', timeout: 60000 });
|
|
await sleep(3000);
|
|
|
|
// Bypass age gate if present
|
|
const ageGate = await page.$('[data-testid="age-gate-modal"]');
|
|
if (ageGate) {
|
|
console.log('[1] Age gate detected, bypassing...');
|
|
const btn = await page.$('[data-testid="age-gate-submit-button"]');
|
|
if (btn) await btn.click();
|
|
await sleep(2000);
|
|
}
|
|
|
|
// Get all navigation links
|
|
console.log('\n[2] Extracting navigation structure...');
|
|
const navInfo = await page.evaluate(() => {
|
|
const links: { text: string; href: string }[] = [];
|
|
|
|
// Look for nav links
|
|
document.querySelectorAll('nav a, [class*="nav"] a, [class*="menu"] a, header a').forEach(el => {
|
|
const text = el.textContent?.trim() || '';
|
|
const href = el.getAttribute('href') || '';
|
|
if (text && href && !links.some(l => l.href === href)) {
|
|
links.push({ text, href });
|
|
}
|
|
});
|
|
|
|
// Look for category tabs/buttons
|
|
document.querySelectorAll('[class*="category"], [class*="tab"], [role="tab"]').forEach(el => {
|
|
const text = el.textContent?.trim() || '';
|
|
const href = el.getAttribute('href') || el.getAttribute('data-href') || '';
|
|
if (text && !links.some(l => l.text === text)) {
|
|
links.push({ text, href: href || `(click: ${el.className})` });
|
|
}
|
|
});
|
|
|
|
// Get current URL
|
|
const currentUrl = window.location.href;
|
|
|
|
// Count products on page
|
|
const productCount = document.querySelectorAll('[class*="product_product__"]').length;
|
|
|
|
return { links, currentUrl, productCount };
|
|
});
|
|
|
|
console.log(`Current URL: ${navInfo.currentUrl}`);
|
|
console.log(`Products on homepage: ${navInfo.productCount}`);
|
|
console.log('\nNavigation links found:');
|
|
navInfo.links.forEach(l => {
|
|
console.log(` "${l.text}" → ${l.href}`);
|
|
});
|
|
|
|
// Look for category buttons/tabs specifically
|
|
console.log('\n[3] Looking for category navigation...');
|
|
const categories = await page.evaluate(() => {
|
|
const cats: { text: string; className: string; tagName: string }[] = [];
|
|
|
|
// Find all clickable elements that might be categories
|
|
const selectors = [
|
|
'[class*="CategoryNav"]',
|
|
'[class*="category"]',
|
|
'[class*="Category"]',
|
|
'[class*="nav"] button',
|
|
'[class*="tab"]',
|
|
'[role="tablist"] *',
|
|
'.MuiTab-root',
|
|
'[class*="filter"]',
|
|
];
|
|
|
|
selectors.forEach(sel => {
|
|
document.querySelectorAll(sel).forEach(el => {
|
|
const text = el.textContent?.trim() || '';
|
|
if (text && text.length < 50 && !cats.some(c => c.text === text)) {
|
|
cats.push({
|
|
text,
|
|
className: el.className?.toString().slice(0, 80) || '',
|
|
tagName: el.tagName,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
return cats;
|
|
});
|
|
|
|
console.log('Category-like elements:');
|
|
categories.forEach(c => {
|
|
console.log(` [${c.tagName}] "${c.text}" (class: ${c.className})`);
|
|
});
|
|
|
|
// Try clicking on "Flower" or "All" if found
|
|
console.log('\n[4] Looking for "Flower" or "All Products" link...');
|
|
const clickTargets = ['Flower', 'All', 'All Products', 'Shop All', 'View All'];
|
|
|
|
for (const target of clickTargets) {
|
|
const element = await page.evaluate((targetText) => {
|
|
const els = Array.from(document.querySelectorAll('a, button, [role="tab"], [class*="category"]'));
|
|
const match = els.find(el =>
|
|
el.textContent?.trim().toLowerCase() === targetText.toLowerCase()
|
|
);
|
|
if (match) {
|
|
return {
|
|
found: true,
|
|
text: match.textContent?.trim(),
|
|
tag: match.tagName,
|
|
};
|
|
}
|
|
return { found: false };
|
|
}, target);
|
|
|
|
if (element.found) {
|
|
console.log(`Found "${element.text}" (${element.tag}), clicking...`);
|
|
|
|
await page.evaluate((targetText) => {
|
|
const els = Array.from(document.querySelectorAll('a, button, [role="tab"], [class*="category"]'));
|
|
const match = els.find(el =>
|
|
el.textContent?.trim().toLowerCase() === targetText.toLowerCase()
|
|
);
|
|
if (match) (match as HTMLElement).click();
|
|
}, target);
|
|
|
|
await sleep(3000);
|
|
|
|
const newUrl = page.url();
|
|
const newCount = await page.evaluate(() =>
|
|
document.querySelectorAll('[class*="product_product__"]').length
|
|
);
|
|
|
|
console.log(` New URL: ${newUrl}`);
|
|
console.log(` Products after click: ${newCount}`);
|
|
|
|
if (newCount > navInfo.productCount) {
|
|
console.log(` ✓ Found more products! (${navInfo.productCount} → ${newCount})`);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check page height and scroll behavior
|
|
console.log('\n[5] Checking scroll behavior on current page...');
|
|
let previousHeight = 0;
|
|
let scrollCount = 0;
|
|
let previousProductCount = await page.evaluate(() =>
|
|
document.querySelectorAll('[class*="product_product__"]').length
|
|
);
|
|
|
|
while (scrollCount < 10) {
|
|
const currentHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
|
|
if (currentHeight === previousHeight) {
|
|
console.log(` Scroll ${scrollCount + 1}: No height change, stopping`);
|
|
break;
|
|
}
|
|
|
|
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
await sleep(1500);
|
|
|
|
const currentProductCount = await page.evaluate(() =>
|
|
document.querySelectorAll('[class*="product_product__"]').length
|
|
);
|
|
|
|
console.log(` Scroll ${scrollCount + 1}: height=${currentHeight}, products=${currentProductCount}`);
|
|
|
|
if (currentProductCount === previousProductCount && scrollCount > 2) {
|
|
console.log(' No new products loading, stopping');
|
|
break;
|
|
}
|
|
|
|
previousHeight = currentHeight;
|
|
previousProductCount = currentProductCount;
|
|
scrollCount++;
|
|
}
|
|
|
|
// Try direct URL patterns
|
|
console.log('\n[6] Testing URL patterns...');
|
|
const urlPatterns = [
|
|
'/onlinemenu/flower?customerType=ADULT',
|
|
'/onlinemenu/all?customerType=ADULT',
|
|
'/onlinemenu?category=flower&customerType=ADULT',
|
|
'/onlinemenu?view=all&customerType=ADULT',
|
|
];
|
|
|
|
for (const pattern of urlPatterns) {
|
|
const testUrl = `https://${STORE_ID}.treez.io${pattern}`;
|
|
console.log(`\nTrying: ${testUrl}`);
|
|
|
|
await page.goto(testUrl, { waitUntil: 'networkidle2', timeout: 30000 });
|
|
await sleep(2000);
|
|
|
|
// Bypass age gate again if needed
|
|
const gate = await page.$('[data-testid="age-gate-modal"]');
|
|
if (gate) {
|
|
const btn = await page.$('[data-testid="age-gate-submit-button"]');
|
|
if (btn) await btn.click();
|
|
await sleep(2000);
|
|
}
|
|
|
|
const productCount = await page.evaluate(() =>
|
|
document.querySelectorAll('[class*="product_product__"]').length
|
|
);
|
|
|
|
console.log(` Products found: ${productCount}`);
|
|
}
|
|
|
|
// Screenshot the final state
|
|
await page.screenshot({ path: '/tmp/treez-explore.png', fullPage: true });
|
|
console.log('\n[7] Screenshot saved to /tmp/treez-explore.png');
|
|
|
|
} catch (error: any) {
|
|
console.error('Error:', error.message);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
main().catch(console.error);
|