Files
cannaiq/backend/dist/utils/age-gate-playwright.js
Kelly 66e07b2009 fix(monitor): remove non-existent worker columns from job_run_logs query
The job_run_logs table tracks scheduled job orchestration, not individual
worker jobs. Worker info (worker_id, worker_hostname) belongs on
dispensary_crawl_jobs, not job_run_logs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 18:45:05 -07:00

176 lines
7.4 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasAgeGatePlaywright = hasAgeGatePlaywright;
exports.bypassAgeGatePlaywright = bypassAgeGatePlaywright;
exports.detectStateFromUrlPlaywright = detectStateFromUrlPlaywright;
const logger_1 = require("../services/logger");
/**
* Detects if a Playwright page has an age verification gate
*/
async function hasAgeGatePlaywright(page) {
try {
const url = page.url();
const bodyText = await page.textContent('body') || '';
const hasAgeVerification = url.includes('/age-gate') ||
bodyText.includes('age verification') ||
bodyText.includes('Please select your state') ||
bodyText.includes('are you 21') ||
bodyText.includes('are you 18') ||
bodyText.includes('Enter your date of birth') ||
bodyText.toLowerCase().includes('verify your age');
return hasAgeVerification;
}
catch (err) {
logger_1.logger.warn('age-gate', `Error detecting age gate: ${err}`);
return false;
}
}
/**
* Attempts to bypass an age gate using Playwright
* Handles multiple age gate patterns including Curaleaf's complex React-based gate
*
* @param page - Playwright page object
* @param state - State to select (e.g., 'Arizona', 'California')
* @returns Promise<boolean> - true if bypass succeeded, false otherwise
*/
async function bypassAgeGatePlaywright(page, state = 'Arizona') {
try {
const hasGate = await hasAgeGatePlaywright(page);
if (!hasGate) {
logger_1.logger.info('age-gate', 'No age gate detected');
return true;
}
logger_1.logger.info('age-gate', `Age gate detected - attempting to bypass with state: ${state}...`);
// Wait for age gate to fully render
await page.waitForTimeout(2000);
// Method 1: Curaleaf-style (state dropdown + "I'm over 21" button)
try {
const stateButton = page.locator('button#state, button[id="state"]').first();
const stateButtonExists = await stateButton.count() > 0;
if (stateButtonExists) {
logger_1.logger.info('age-gate', 'Found Curaleaf-style state dropdown...');
await stateButton.click();
await page.waitForTimeout(1000);
// Select state
const stateOption = page.locator('[role="option"]').filter({ hasText: new RegExp(`^${state}$`, 'i') });
const stateExists = await stateOption.count() > 0;
if (stateExists) {
logger_1.logger.info('age-gate', `Clicking ${state} option...`);
await stateOption.first().click();
await page.waitForTimeout(2000);
// Look for "I'm over 21" button
const ageButton = page.locator('button').filter({ hasText: /I'm over 21|I am 21|I'm 21|over 21/i });
const ageButtonExists = await ageButton.count() > 0;
if (ageButtonExists) {
logger_1.logger.info('age-gate', 'Clicking age verification button...');
await ageButton.first().click();
await page.waitForLoadState('domcontentloaded', { timeout: 15000 });
await page.waitForTimeout(3000);
// Check if we successfully bypassed
const finalUrl = page.url();
if (!finalUrl.includes('/age-gate')) {
logger_1.logger.info('age-gate', `✅ Age gate bypass successful`);
return true;
}
}
}
}
}
catch (e) {
logger_1.logger.warn('age-gate', `Curaleaf method failed: ${e}`);
}
// Method 2: Simple "Yes" or "I'm 21" button (for simpler age gates)
try {
const simpleButton = page.locator('button, a, [role="button"]').filter({
hasText: /yes|i am 21|i'm 21|enter the site|continue|confirm/i
});
const simpleExists = await simpleButton.count() > 0;
if (simpleExists) {
logger_1.logger.info('age-gate', 'Found simple age gate button...');
await simpleButton.first().click();
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
await page.waitForTimeout(2000);
const finalUrl = page.url();
if (!finalUrl.includes('/age-gate')) {
logger_1.logger.info('age-gate', `✅ Age gate bypass successful`);
return true;
}
}
}
catch (e) {
logger_1.logger.warn('age-gate', `Simple button method failed: ${e}`);
}
// Method 3: Standard select dropdown
try {
const selectExists = await page.locator('select').count() > 0;
if (selectExists) {
logger_1.logger.info('age-gate', 'Found select dropdown...');
const select = page.locator('select').first();
await select.selectOption({ label: state });
await page.waitForTimeout(1000);
// Look for submit button
const submitButton = page.locator('button[type="submit"], input[type="submit"]');
const submitExists = await submitButton.count() > 0;
if (submitExists) {
await submitButton.first().click();
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
await page.waitForTimeout(2000);
const finalUrl = page.url();
if (!finalUrl.includes('/age-gate')) {
logger_1.logger.info('age-gate', `✅ Age gate bypass successful`);
return true;
}
}
}
}
catch (e) {
logger_1.logger.warn('age-gate', `Select dropdown method failed: ${e}`);
}
// Verify final state
const finalUrl = page.url();
if (finalUrl.includes('/age-gate')) {
logger_1.logger.error('age-gate', `❌ Age gate bypass failed - still at: ${finalUrl}`);
return false;
}
logger_1.logger.info('age-gate', `✅ Age gate bypass successful`);
return true;
}
catch (err) {
logger_1.logger.error('age-gate', `Error bypassing age gate: ${err}`);
return false;
}
}
/**
* Helper to detect the state from a store URL
*/
function detectStateFromUrlPlaywright(url) {
const stateMap = {
'-az-': 'Arizona',
'arizona': 'Arizona',
'-ca-': 'California',
'california': 'California',
'-co-': 'Colorado',
'colorado': 'Colorado',
'-fl-': 'Florida',
'florida': 'Florida',
'-il-': 'Illinois',
'illinois': 'Illinois',
'-ma-': 'Massachusetts',
'-mi-': 'Michigan',
'-nv-': 'Nevada',
'-nj-': 'New Jersey',
'-ny-': 'New York',
'-or-': 'Oregon',
'-pa-': 'Pennsylvania',
'-wa-': 'Washington',
};
const lowerUrl = url.toLowerCase();
for (const [pattern, stateName] of Object.entries(stateMap)) {
if (lowerUrl.includes(pattern)) {
return stateName;
}
}
// Default to Arizona
return 'Arizona';
}