← 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(() => {});
}
})();