- 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>
133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
/**
|
|
* Treez High-Level Query Functions
|
|
*
|
|
* Wraps the low-level client methods with business logic
|
|
* for common operations like product fetching.
|
|
*/
|
|
|
|
import {
|
|
startSession,
|
|
endSession,
|
|
fetchAllProducts,
|
|
buildMenuUrl,
|
|
TreezProductRaw,
|
|
TreezStoreInfo,
|
|
} from './client';
|
|
|
|
// ============================================================
|
|
// PRODUCT OPERATIONS
|
|
// ============================================================
|
|
|
|
export interface FetchProductsResult {
|
|
store: TreezStoreInfo;
|
|
products: TreezProductRaw[];
|
|
totalCaptured: number;
|
|
scrollCount: number;
|
|
}
|
|
|
|
/**
|
|
* Fetch all products from a Treez store
|
|
*
|
|
* @param storeId - Treez store ID (slug like "best")
|
|
* @returns Products and store data captured from the page
|
|
*/
|
|
export async function fetchProductsByStoreId(storeId: string): Promise<FetchProductsResult> {
|
|
try {
|
|
await startSession(storeId);
|
|
|
|
const { products, storeInfo, scrollCount } = await fetchAllProducts(storeId);
|
|
|
|
return {
|
|
store: storeInfo,
|
|
products,
|
|
totalCaptured: products.length,
|
|
scrollCount,
|
|
};
|
|
} finally {
|
|
await endSession();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch products from a Treez menu URL
|
|
* Extracts store ID from URL and fetches products
|
|
*
|
|
* @param menuUrl - Full Treez menu URL
|
|
* @returns Products and store data
|
|
*/
|
|
export async function fetchProductsFromUrl(menuUrl: string): Promise<FetchProductsResult> {
|
|
const storeId = extractStoreIdFromUrl(menuUrl);
|
|
if (!storeId) {
|
|
throw new Error(`Could not extract store ID from URL: ${menuUrl}`);
|
|
}
|
|
|
|
return fetchProductsByStoreId(storeId);
|
|
}
|
|
|
|
// ============================================================
|
|
// STORE OPERATIONS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Extract store ID from a Treez URL
|
|
*
|
|
* Supports formats:
|
|
* - https://best.treez.io/onlinemenu/
|
|
* - https://shop.bestdispensary.com/ (resolves to best.treez.io)
|
|
*
|
|
* @param url - Treez menu URL
|
|
* @returns Store ID or null if not found
|
|
*/
|
|
export function extractStoreIdFromUrl(url: string): string | null {
|
|
// Pattern 1: {storeId}.treez.io
|
|
const treezMatch = url.match(/https?:\/\/([^.]+)\.treez\.io/i);
|
|
if (treezMatch) {
|
|
return treezMatch[1];
|
|
}
|
|
|
|
// Pattern 2: Custom domain - would need to follow redirect
|
|
// For now, return null and let the caller handle it
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Validate that a store ID exists and is accessible
|
|
*
|
|
* @param storeId - Treez store ID
|
|
* @returns True if store is accessible
|
|
*/
|
|
export async function validateStoreId(storeId: string): Promise<boolean> {
|
|
try {
|
|
await startSession(storeId);
|
|
|
|
const { page } = (await import('./client')).getCurrentSession()!;
|
|
const url = buildMenuUrl(storeId);
|
|
|
|
await page.goto(url, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 30000,
|
|
});
|
|
|
|
// Check if we got a valid page (not 404)
|
|
const title = await page.title();
|
|
const is404 = title.toLowerCase().includes('404') || title.toLowerCase().includes('not found');
|
|
|
|
return !is404;
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
await endSession();
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get the direct Treez menu URL for a store
|
|
*/
|
|
export function getMenuUrl(storeId: string, customerType: 'ADULT' | 'MEDICAL' = 'ADULT'): string {
|
|
return buildMenuUrl(storeId, customerType);
|
|
}
|