← back to Ventura Corridor

scripts/foursquare_signup.cjs

209 lines

/**
 * Autonomous Foursquare developer-portal signup via browserbase.
 *
 * Hard stops (abort + report immediately):
 *   - reCAPTCHA / hCaptcha / Cloudflare Turnstile
 *   - SMS / phone verification
 *   - Credit card requirement
 *
 * On success: prints the page state and (if reachable) the API key.
 * On any hard stop: prints a clean message + the live debugger URL so the
 *   human can finish the last step in a real browser.
 */
require('dotenv').config({ path: require('os').homedir() + '/.claude/skills/browserbase/.env' });
const Browserbase = require('@browserbasehq/sdk').default;
const { chromium } = require('playwright-core');
const fs = require('fs');
const path = require('path');

const PROJECT_ID = process.env.BROWSERBASE_PROJECT_ID;
const API_KEY = process.env.BROWSERBASE_API_KEY;
if (!PROJECT_ID || !API_KEY) {
  console.error('[fsq-signup] missing BROWSERBASE_PROJECT_ID / BROWSERBASE_API_KEY in ~/.claude/skills/browserbase/.env');
  process.exit(1);
}

// Pull the portal password from secrets-manager .env (not echoed).
const SECRETS_ENV = require('os').homedir() + '/Projects/secrets-manager/.env';
let PORTAL_PW = '';
try {
  const txt = fs.readFileSync(SECRETS_ENV, 'utf8');
  const m = txt.match(/^FOURSQUARE_PORTAL_PASSWORD=(.+)$/m);
  if (m) PORTAL_PW = m[1].trim();
} catch {}
if (!PORTAL_PW) {
  console.error('[fsq-signup] FOURSQUARE_PORTAL_PASSWORD not found in secrets-manager .env');
  process.exit(1);
}

const EMAIL = 'steveabramsdesigns@gmail.com';
const FIRST_NAME = 'Steve';
const LAST_NAME = 'Abrams';
const SIGNUP_URL = 'https://foursquare.com/developers/signup/';

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function detectHardStop(page) {
  const html = await page.content();
  const lc = html.toLowerCase();
  if (/cf-turnstile|turnstile|cloudflare/i.test(html) && /challenge|captcha|verify you are human/i.test(html)) {
    return 'cloudflare-turnstile';
  }
  if (/recaptcha\/api\.js|grecaptcha/.test(html)) return 'recaptcha';
  if (/hcaptcha\.com|h-captcha/i.test(html)) return 'hcaptcha';
  if (/(sms|phone).*(verification|verify|code)/i.test(lc) && /enter.*code/i.test(lc)) return 'sms-phone';
  if (/credit card|payment method|card number/i.test(lc)) return 'credit-card';
  return null;
}

async function dumpState(page, tag) {
  try {
    const url = page.url();
    const title = await page.title().catch(() => '');
    const text = (await page.evaluate(() => document.body.innerText).catch(() => '')).slice(0, 600);
    console.log(`[${tag}] url:    ${url}`);
    console.log(`[${tag}] title:  ${title}`);
    console.log(`[${tag}] text:   ${text.replace(/\s+/g, ' ').slice(0, 400)}`);
  } catch (e) {
    console.log(`[${tag}] dump failed: ${e.message}`);
  }
}

async function tryFill(page, selectors, value) {
  for (const sel of selectors) {
    try {
      await page.fill(sel, value, { timeout: 1500 });
      return sel;
    } catch {}
  }
  return null;
}

async function tryClick(page, selectors) {
  for (const sel of selectors) {
    try {
      await page.click(sel, { timeout: 1500 });
      return sel;
    } catch {}
  }
  return null;
}

(async () => {
  const bb = new Browserbase({ apiKey: API_KEY });
  const session = await bb.sessions.create({ projectId: PROJECT_ID });
  console.log(`[fsq-signup] browserbase session: ${session.id}`);
  console.log(`[fsq-signup] live debugger:        ${session.debuggerUrl || session.connectUrl}`);

  const browser = await chromium.connectOverCDP(session.connectUrl);
  const ctx = browser.contexts()[0];
  const page = ctx.pages()[0] || await ctx.newPage();
  page.setDefaultTimeout(20_000);

  try {
    console.log('[fsq-signup] navigating to', SIGNUP_URL);
    await page.goto(SIGNUP_URL, { waitUntil: 'networkidle', timeout: 30_000 });
    await sleep(1500);
    await dumpState(page, 'arrived');

    // Hard-stop check on landing
    let stop = await detectHardStop(page);
    if (stop) {
      console.log(`[HARD-STOP] ${stop} on signup landing — abort. Finish manually at ${page.url()}`);
      console.log(`[fsq-signup] LIVE_DEBUG=${session.debuggerUrl}`);
      await browser.close();
      process.exit(2);
    }

    // The Auth0 universal-login flow has TWO steps: identifier (email) then password.
    // OR it offers "Sign Up" as a separate tab. Try the "Sign Up" link first.
    const signUpClicked = await tryClick(page, [
      'a:has-text("Sign Up")',
      'a:has-text("Sign up")',
      'button:has-text("Sign Up")',
      'button:has-text("Create account")',
      'a[href*="signup"]',
    ]);
    if (signUpClicked) {
      console.log(`[fsq-signup] clicked sign-up link: ${signUpClicked}`);
      await sleep(2000);
      await dumpState(page, 'post-signup-click');
    }

    // Fill email
    const emailFilled = await tryFill(page, [
      'input[name="email"]', 'input[type="email"]', 'input[autocomplete="email"]', '#email',
    ], EMAIL);
    if (!emailFilled) {
      console.log('[fsq-signup] could not find email field — page may have changed.');
      await dumpState(page, 'no-email-field');
      console.log(`[fsq-signup] LIVE_DEBUG=${session.debuggerUrl}`);
      await browser.close();
      process.exit(3);
    }
    console.log(`[fsq-signup] email filled (${emailFilled})`);

    // Fill password if a password field is present (some flows ask after email)
    const pwFilled = await tryFill(page, [
      'input[name="password"]', 'input[type="password"]', 'input[autocomplete="new-password"]',
    ], PORTAL_PW);
    if (pwFilled) console.log(`[fsq-signup] password filled (${pwFilled})`);

    // Optional: name fields
    await tryFill(page, ['input[name="firstName"]', 'input[name="given_name"]', '#firstName'], FIRST_NAME);
    await tryFill(page, ['input[name="lastName"]', 'input[name="family_name"]', '#lastName'], LAST_NAME);

    // Accept ToS checkbox if present (best-effort)
    try { await page.check('input[type="checkbox"][name*="terms" i], input[type="checkbox"][name*="agree" i]', { timeout: 1500 }); } catch {}

    // Submit
    const submitClicked = await tryClick(page, [
      'button[type="submit"]',
      'button:has-text("Continue")',
      'button:has-text("Sign Up")',
      'button:has-text("Sign up")',
      'button:has-text("Create account")',
    ]);
    if (!submitClicked) {
      console.log('[fsq-signup] could not find submit — abort.');
      await dumpState(page, 'no-submit');
      console.log(`[fsq-signup] LIVE_DEBUG=${session.debuggerUrl}`);
      await browser.close();
      process.exit(4);
    }
    console.log(`[fsq-signup] submitted (${submitClicked})`);

    // Wait for next state
    await sleep(4000);
    await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
    await dumpState(page, 'post-submit');

    stop = await detectHardStop(page);
    if (stop) {
      console.log(`[HARD-STOP] ${stop} after submit — abort.`);
      console.log(`[fsq-signup] LIVE_DEBUG=${session.debuggerUrl}`);
      await browser.close();
      process.exit(5);
    }

    // Detect email-verification wall
    const text = (await page.evaluate(() => document.body.innerText).catch(() => '')).toLowerCase();
    if (/check your email|verify your email|sent.*email|confirm.*email/i.test(text)) {
      console.log('[fsq-signup] email-verification wall — Foursquare sent a confirmation to', EMAIL);
      console.log('[fsq-signup] LIVE_DEBUG=' + session.debuggerUrl);
      console.log('[fsq-signup] Steve: open that email, click the verify link, then re-run this script (the session will detect logged-in state and proceed).');
      await browser.close();
      process.exit(0);
    }

    console.log('[fsq-signup] signup appears to have completed without an obvious wall.');
    console.log('[fsq-signup] page state above. LIVE_DEBUG=' + session.debuggerUrl);

  } catch (e) {
    console.error('[fsq-signup] error:', e.message);
    console.log('[fsq-signup] LIVE_DEBUG=' + (session.debuggerUrl || ''));
  } finally {
    await browser.close().catch(() => {});
  }
})();