Files
cannaiq/backend/scripts/explore-treez-structure.ts
Kelly 023cfc127f fix(preflight): Apply stored fingerprint to task browser
- 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>
2025-12-13 16:40:52 -07:00

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);