diff --git a/.gitignore b/.gitignore index 8f71597..f25fd0f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules/ .github/ diagnostics/ reports/ +accounts-created/ accounts.json accounts.jsonc notes diff --git a/README.md b/README.md index b940170..7f28017 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,36 @@ npm run buy 1 # By account number --- +## πŸ†• Account Creator + +Automatically create new Microsoft accounts with referral link support: + +```bash +# Create account without referral +npm run creator + +# Create account with your referral link +npm run creator https://rewards.bing.com/welcome?rh=YOUR_CODE&ref=rafsrchae +``` + +**Features:** +- 🎯 Language-independent (works in any language) +- πŸ” Generates strong passwords automatically +- πŸ“§ Creates unique email addresses +- πŸŽ‚ Realistic birthdates (18-50 years old) +- πŸ€– CAPTCHA support (manual solving required) +- πŸ’Ύ Saves all account details to `accounts-created/` directory + +**What happens:** +1. Opens browser to Microsoft signup page +2. Automatically fills email, password, birthdate, and name +3. Waits for you to solve CAPTCHA +4. Saves complete account info to file + +**[πŸ“– Full Account Creator Guide](src/account-creation/README.md)** + +--- + ## ⏰ Automatic Scheduling Configure automatic task scheduling directly from `config.jsonc` - **perfect for Raspberry Pi!** diff --git a/package.json b/package.json index 6daab26..8be88d6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "ts-start": "node --loader ts-node/esm ./src/index.ts", "dev": "ts-node ./src/index.ts -dev", "buy": "node --enable-source-maps ./dist/index.js -buy", + "creator": "ts-node ./src/account-creation/cli.ts", "dashboard": "node --enable-source-maps ./dist/index.js -dashboard", "dashboard-dev": "ts-node ./src/index.ts -dashboard", "lint": "eslint \"src/**/*.{ts,tsx}\"", diff --git a/src/account-creation/AccountCreator.ts b/src/account-creation/AccountCreator.ts new file mode 100644 index 0000000..a8993eb --- /dev/null +++ b/src/account-creation/AccountCreator.ts @@ -0,0 +1,1615 @@ +import type { Page, BrowserContext } from 'playwright' +import { log } from '../util/Logger' +import { DataGenerator } from './DataGenerator' +import { CreatedAccount } from './types' +import fs from 'fs' +import path from 'path' +import * as readline from 'readline' + +export class AccountCreator { + private page!: Page + private dataGenerator: DataGenerator + private referralUrl?: string + private rl: readline.Interface + + constructor(referralUrl?: string) { + this.referralUrl = referralUrl + this.dataGenerator = new DataGenerator() + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + } + + // Human-like delay helper + private async humanDelay(minMs: number, maxMs: number): Promise { + const delay = Math.random() * (maxMs - minMs) + minMs + await this.page.waitForTimeout(Math.floor(delay)) + } + + private async askQuestion(question: string): Promise { + return new Promise((resolve) => { + this.rl.question(question, (answer) => { + resolve(answer.trim()) + }) + }) + } + + /** + * CRITICAL: Wait for page to be completely stable before continuing + * Checks for loading spinners, network activity, and URL stability + */ + private async waitForPageStable(context: string, maxWaitMs: number = 30000): Promise { + log(false, 'CREATOR', `[${context}] Waiting for page to be stable...`, 'log', 'cyan') + + const startTime = Date.now() + const startUrl = this.page.url() + + try { + // Wait for network to be idle + await this.page.waitForLoadState('networkidle', { timeout: maxWaitMs }) + log(false, 'CREATOR', `[${context}] βœ… Network idle`, 'log', 'green') + + // Additional delay to ensure everything is rendered + await this.humanDelay(2000, 3000) + + // Check for loading indicators + const loadingSelectors = [ + '.loading', + '[class*="spinner"]', + '[class*="loading"]', + '[aria-busy="true"]', + 'div:has-text("Loading")', + 'div:has-text("Chargement")', + 'div:has-text("Please wait")', + 'div:has-text("Veuillez patienter")', + 'div:has-text("Creating")', + 'div:has-text("CrΓ©ation")' + ] + + // Wait for all loading indicators to disappear + for (const selector of loadingSelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `[${context}] Loading indicator detected: ${selector}`, 'log', 'yellow') + + try { + await element.waitFor({ state: 'hidden', timeout: maxWaitMs - (Date.now() - startTime) }) + log(false, 'CREATOR', `[${context}] βœ… Loading indicator gone`, 'log', 'green') + } catch { + log(false, 'CREATOR', `[${context}] ⚠️ Loading indicator still present`, 'warn', 'yellow') + } + } + } + + // Verify URL hasn't changed during wait (unless we expect it to) + const endUrl = this.page.url() + if (startUrl !== endUrl) { + log(false, 'CREATOR', `[${context}] URL changed: ${startUrl} β†’ ${endUrl}`, 'log', 'yellow') + } + + const elapsed = Date.now() - startTime + log(false, 'CREATOR', `[${context}] βœ… Page is stable (waited ${elapsed}ms)`, 'log', 'green') + + return true + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `[${context}] ⚠️ Page stability check failed: ${msg}`, 'warn', 'yellow') + return false + } + } + + /** + * CRITICAL: Wait for Microsoft account creation to complete + * This happens AFTER CAPTCHA and can take several seconds + */ + private async waitForAccountCreation(): Promise { + log(false, 'CREATOR', '⏳ Waiting for Microsoft to create the account...', 'log', 'cyan') + + const maxWaitTime = 60000 // 60 seconds + const startTime = Date.now() + const startUrl = this.page.url() + + try { + // STEP 1: Wait for any "Creating account" messages to appear AND disappear + const creationMessages = [ + 'text="Creating your account"', + 'text="CrΓ©ation de votre compte"', + 'text="Setting up your account"', + 'text="Configuration de votre compte"', + 'text="Please wait"', + 'text="Veuillez patienter"' + ] + + for (const messageSelector of creationMessages) { + const element = this.page.locator(messageSelector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Account creation message detected: "${messageSelector}"`, 'log', 'yellow') + + // Wait for this message to disappear + try { + await element.waitFor({ state: 'hidden', timeout: 45000 }) + log(false, 'CREATOR', 'βœ… Account creation message gone', 'log', 'green') + } catch { + log(false, 'CREATOR', '⚠️ Creation message still present after 45s', 'warn', 'yellow') + } + } + } + + // STEP 2: Wait for URL to stabilize or change to expected page + log(false, 'CREATOR', 'Waiting for navigation to complete...', 'log', 'cyan') + + let urlStableCount = 0 + let lastUrl = this.page.url() + + while (Date.now() - startTime < maxWaitTime) { + await this.humanDelay(1000, 1500) + + const currentUrl = this.page.url() + + if (currentUrl === lastUrl) { + urlStableCount++ + + // URL has been stable for 3 consecutive checks + if (urlStableCount >= 3) { + log(false, 'CREATOR', `βœ… URL stable at: ${currentUrl}`, 'log', 'green') + break + } + } else { + log(false, 'CREATOR', `URL changed: ${lastUrl} β†’ ${currentUrl}`, 'log', 'yellow') + lastUrl = currentUrl + urlStableCount = 0 + } + } + + // STEP 3: Wait for page to be fully loaded + await this.waitForPageStable('ACCOUNT_CREATION', 15000) + + // STEP 4: Additional safety delay + await this.humanDelay(3000, 5000) + + const elapsed = Date.now() - startTime + log(false, 'CREATOR', `βœ… Account creation complete (waited ${elapsed}ms)`, 'log', 'green') + + return true + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `⚠️ Account creation wait failed: ${msg}`, 'warn', 'yellow') + return false + } + } + + /** + * CRITICAL: Verify that an element exists, is visible, and is interactable + */ + private async verifyElementReady( + selector: string, + context: string, + timeoutMs: number = 10000 + ): Promise { + try { + const element = this.page.locator(selector).first() + + // Wait for element to exist + await element.waitFor({ timeout: timeoutMs, state: 'attached' }) + + // Wait for element to be visible + await element.waitFor({ timeout: 5000, state: 'visible' }) + + // Check if element is enabled (for buttons/inputs) + const isEnabled = await element.isEnabled().catch(() => true) + + if (!isEnabled) { + log(false, 'CREATOR', `[${context}] Element found but disabled: ${selector}`, 'warn', 'yellow') + return false + } + + log(false, 'CREATOR', `[${context}] βœ… Element ready: ${selector}`, 'log', 'green') + return true + + } catch (error) { + log(false, 'CREATOR', `[${context}] Element not ready: ${selector}`, 'warn', 'yellow') + return false + } + } + + async create(context: BrowserContext): Promise { + try { + this.page = await context.newPage() + + log(false, 'CREATOR', 'Starting account creation process...', 'log', 'cyan') + + // Navigate to signup page + await this.navigateToSignup() + + // Click "Create account" button + await this.clickCreateAccount() + + // Generate email and fill it (handles suggestions automatically) + const emailResult = await this.generateAndFillEmail() + if (!emailResult) { + log(false, 'CREATOR', 'Failed to configure email', 'error') + return null + } + + log(false, 'CREATOR', `Email accepted: ${emailResult}`, 'log', 'green') + + // Wait for password page and fill it + const password = await this.fillPassword() + if (!password) { + log(false, 'CREATOR', 'Failed to generate password', 'error') + return null + } + + // Click Next button + await this.clickNext('password') + + // Extract final email from identity badge to confirm + const finalEmail = await this.extractEmail() + const confirmedEmail = finalEmail || emailResult + + log(false, 'CREATOR', `Using email: ${confirmedEmail}`, 'log', 'green') + + // Fill birthdate + const birthdate = await this.fillBirthdate() + if (!birthdate) { + log(false, 'CREATOR', 'Failed to fill birthdate', 'error') + return null + } + + // Click Next button + await this.clickNext('birthdate') + + // Fill name fields + const names = await this.fillNames(confirmedEmail) + if (!names) { + log(false, 'CREATOR', 'Failed to fill names', 'error') + return null + } + + // Click Next button + await this.clickNext('names') + + // Wait for CAPTCHA page + const captchaDetected = await this.waitForCaptcha() + if (captchaDetected) { + log(false, 'CREATOR', '⚠️ CAPTCHA detected - waiting for human to solve it...', 'warn', 'yellow') + log(false, 'CREATOR', 'Please solve the CAPTCHA in the browser. The script will wait...', 'log', 'yellow') + + await this.waitForCaptchaSolved() + + log(false, 'CREATOR', 'βœ… CAPTCHA solved! Continuing...', 'log', 'green') + } + + // Handle post-CAPTCHA questions (Stay signed in, etc.) + await this.handlePostCreationQuestions() + + // Navigate to Bing Rewards and verify connection + await this.verifyAccountActive() + + // Create account object + const createdAccount: CreatedAccount = { + email: confirmedEmail, + password: password, + birthdate: { + day: birthdate.day, + month: birthdate.month, + year: birthdate.year + }, + firstName: names.firstName, + lastName: names.lastName, + createdAt: new Date().toISOString(), + referralUrl: this.referralUrl + } + + // Save to file + await this.saveAccount(createdAccount) + + log(false, 'CREATOR', `βœ… Account created successfully: ${confirmedEmail}`, 'log', 'green') + + // Cleanup readline interface + this.rl.close() + + return createdAccount + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Error during account creation: ${msg}`, 'error') + log(false, 'CREATOR', '⚠️ Browser left open for inspection. Press Ctrl+C to exit.', 'warn', 'yellow') + + // Keep browser open and wait indefinitely + await new Promise(() => {}) // Never resolves - keeps process alive + + this.rl.close() + return null + } + } + + private async navigateToSignup(): Promise { + if (this.referralUrl) { + log(false, 'CREATOR', `Navigating to referral URL: ${this.referralUrl}`, 'log', 'cyan') + await this.page.goto(this.referralUrl, { waitUntil: 'networkidle', timeout: 60000 }) + await this.humanDelay(1500, 3000) + + log(false, 'CREATOR', 'Looking for "Join Microsoft Rewards" button...', 'log') + + // Multiple selectors for the join button + const joinButtonSelectors = [ + 'a#start-earning-rewards-link', + 'a.cta.learn-more-btn', + 'a[href*="signup"]', + 'button[class*="join"]' + ] + + let clicked = false + for (const selector of joinButtonSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + await button.click() + await this.humanDelay(2000, 4000) + log(false, 'CREATOR', `βœ… Clicked join button with selector: ${selector}`, 'log', 'green') + clicked = true + break + } + } + + if (!clicked) { + log(false, 'CREATOR', 'Could not find join button, continuing anyway...', 'warn', 'yellow') + } + } else { + const url = 'https://login.live.com/' + log(false, 'CREATOR', `No referral URL - navigating to: ${url}`, 'log', 'cyan') + await this.page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }) + await this.humanDelay(1500, 3000) + } + } + + private async clickCreateAccount(): Promise { + log(false, 'CREATOR', 'Looking for "Create account" button...', 'log') + + // Multiple selectors for create account button + const createAccountSelectors = [ + 'a[id*="signup"]', + 'a[href*="signup"]', + 'span[role="button"].fui-Link', + 'button[id*="signup"]', + 'a[data-testid*="signup"]' + ] + + for (const selector of createAccountSelectors) { + const button = this.page.locator(selector).first() + + try { + await button.waitFor({ timeout: 5000 }) + await button.click() + await this.humanDelay(2000, 3500) + + log(false, 'CREATOR', `βœ… Clicked "Create account" with selector: ${selector}`, 'log', 'green') + return + } catch { + continue + } + } + + throw new Error('Could not find "Create account" button with any selector') + } + + private async generateAndFillEmail(): Promise { + log(false, 'CREATOR', '\n=== Email Configuration ===', 'log', 'cyan') + + const useAutoGenerate = await this.askQuestion('Generate email automatically? (Y/n): ') + + let email: string + + if (useAutoGenerate.toLowerCase() === 'n' || useAutoGenerate.toLowerCase() === 'no') { + email = await this.askQuestion('Enter your email: ') + log(false, 'CREATOR', `Using custom email: ${email}`, 'log', 'cyan') + } else { + email = this.dataGenerator.generateEmail() + log(false, 'CREATOR', `Generated realistic email: ${email}`, 'log', 'cyan') + } + + const emailInput = this.page.locator('input[type="email"]').first() + await emailInput.waitFor({ timeout: 15000 }) + await emailInput.clear() + await this.humanDelay(500, 1000) + await emailInput.fill(email) + await this.humanDelay(800, 2000) + + log(false, 'CREATOR', 'Clicking Next button...', 'log') + const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first() + await nextBtn.waitFor({ timeout: 15000 }) + await nextBtn.click() + await this.humanDelay(2000, 3000) + + // Check for any error after clicking Next + const result = await this.handleEmailErrors(email) + if (!result.success) { + return null + } + + return result.email + } + + private async handleEmailErrors(originalEmail: string): Promise<{ success: boolean; email: string | null }> { + // Wait for page to settle + await this.humanDelay(1000, 1500) + + const errorLocator = this.page.locator('div[id*="Error"], div[role="alert"]').first() + const errorVisible = await errorLocator.isVisible().catch(() => false) + + if (!errorVisible) { + log(false, 'CREATOR', `βœ… Email accepted: ${originalEmail}`, 'log', 'green') + return { success: true, email: originalEmail } + } + + const errorText = await errorLocator.textContent().catch(() => '') || '' + log(false, 'CREATOR', `Email error: ${errorText}`, 'warn', 'yellow') + + // Check for reserved domain error + if (errorText && (errorText.toLowerCase().includes('reserved') || errorText.toLowerCase().includes('rΓ©servΓ©'))) { + return await this.handleReservedDomain(originalEmail) + } + + // Check for email taken error + if (errorText && (errorText.toLowerCase().includes('taken') || errorText.toLowerCase().includes('pris') || + errorText.toLowerCase().includes('already') || errorText.toLowerCase().includes('dΓ©jΓ '))) { + return await this.handleEmailTaken() + } + + log(false, 'CREATOR', 'Unknown error type, pausing for inspection', 'error') + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + + private async handleReservedDomain(originalEmail: string): Promise<{ success: boolean; email: string | null }> { + log(false, 'CREATOR', `Domain blocked: ${originalEmail.split('@')[1]}`, 'warn', 'yellow') + + const username = originalEmail.split('@')[0] + const newEmail = `${username}@outlook.com` + + log(false, 'CREATOR', `Retrying with: ${newEmail}`, 'log', 'cyan') + + const emailInput = this.page.locator('input[type="email"]').first() + await emailInput.clear() + await this.humanDelay(500, 1000) + await emailInput.fill(newEmail) + await this.humanDelay(800, 2000) + + const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first() + await nextBtn.click() + await this.humanDelay(2000, 3000) + + // Re-check for errors with the new email + return await this.handleEmailErrors(newEmail) + } + + private async handleEmailTaken(): Promise<{ success: boolean; email: string | null }> { + log(false, 'CREATOR', 'Email taken, looking for Microsoft suggestions...', 'log', 'yellow') + + await this.humanDelay(2000, 3000) + + // Multiple selectors for suggestions container + const suggestionSelectors = [ + 'div[data-testid="suggestions"]', + 'div[role="toolbar"]', + 'div.fui-TagGroup', + 'div[class*="suggestions"]' + ] + + let suggestionsContainer = null + for (const selector of suggestionSelectors) { + const container = this.page.locator(selector).first() + const visible = await container.isVisible().catch(() => false) + if (visible) { + suggestionsContainer = container + log(false, 'CREATOR', `Found suggestions with selector: ${selector}`, 'log', 'green') + break + } + } + + if (!suggestionsContainer) { + log(false, 'CREATOR', 'No suggestions found', 'warn', 'yellow') + + // Debug: check HTML content + const pageContent = await this.page.content() + const hasDataTestId = pageContent.includes('data-testid="suggestions"') + const hasToolbar = pageContent.includes('role="toolbar"') + log(false, 'CREATOR', `Debug - suggestions in HTML: ${hasDataTestId}, toolbar: ${hasToolbar}`, 'warn', 'yellow') + + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + + // Find all suggestion buttons + const suggestionButtons = await suggestionsContainer.locator('button').all() + log(false, 'CREATOR', `Found ${suggestionButtons.length} suggestion buttons`, 'log', 'cyan') + + if (suggestionButtons.length === 0) { + log(false, 'CREATOR', 'Suggestions container found but no buttons inside', 'error') + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + + // Get text from first suggestion before clicking + const firstButton = suggestionButtons[0] + if (!firstButton) { + log(false, 'CREATOR', 'First button is undefined', 'error') + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + + const suggestedEmail = await firstButton.textContent().catch(() => '') || '' + let cleanEmail = suggestedEmail.trim() + + // If suggestion doesn't have @domain, it's just the username - add @outlook.com + if (cleanEmail && !cleanEmail.includes('@')) { + cleanEmail = `${cleanEmail}@outlook.com` + log(false, 'CREATOR', `Suggestion is username only, adding domain: ${cleanEmail}`, 'log', 'cyan') + } + + if (!cleanEmail) { + log(false, 'CREATOR', 'Could not extract email from suggestion button', 'error') + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + + log(false, 'CREATOR', `Selecting suggestion: ${cleanEmail}`, 'log', 'cyan') + + // Click the suggestion + await firstButton.click() + await this.humanDelay(1500, 2500) + + // Verify the email input was updated + const emailInput = this.page.locator('input[type="email"]').first() + const inputValue = await emailInput.inputValue().catch(() => '') + + if (inputValue) { + log(false, 'CREATOR', `βœ… Suggestion applied: ${inputValue}`, 'log', 'green') + } + + // Check if error is gone + const errorLocator = this.page.locator('div[id*="Error"], div[role="alert"]').first() + const errorStillVisible = await errorLocator.isVisible().catch(() => false) + + if (errorStillVisible) { + log(false, 'CREATOR', 'Error still visible after clicking suggestion', 'warn', 'yellow') + + // Try clicking Next to submit + const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first() + const nextEnabled = await nextBtn.isEnabled().catch(() => false) + + if (nextEnabled) { + log(false, 'CREATOR', 'Clicking Next to submit suggestion', 'log') + await nextBtn.click() + await this.humanDelay(2000, 3000) + + // Final check + const finalError = await errorLocator.isVisible().catch(() => false) + if (finalError) { + log(false, 'CREATOR', 'Failed to resolve error', 'error') + log(false, 'CREATOR', '⚠️ Browser left open. Press Ctrl+C to exit.', 'warn', 'yellow') + await new Promise(() => {}) + return { success: false, email: null } + } + } + } else { + // Error is gone, click Next to continue + log(false, 'CREATOR', 'Suggestion accepted, clicking Next', 'log', 'green') + const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first() + await nextBtn.click() + await this.humanDelay(2000, 3000) + } + + log(false, 'CREATOR', `βœ… Using suggested email: ${cleanEmail}`, 'log', 'green') + return { success: true, email: cleanEmail } + } + + private async clickNext(step: string): Promise { + log(false, 'CREATOR', `Clicking Next button (${step})...`, 'log') + + // CRITICAL: Ensure page is stable before clicking + await this.waitForPageStable(`BEFORE_NEXT_${step.toUpperCase()}`, 15000) + + // Find button by test id or type submit + const nextBtn = this.page.locator('button[data-testid="primaryButton"], button[type="submit"]').first() + + // CRITICAL: Verify button is ready + const isReady = await this.verifyElementReady( + 'button[data-testid="primaryButton"], button[type="submit"]', + `NEXT_BUTTON_${step.toUpperCase()}`, + 15000 + ) + + if (!isReady) { + log(false, 'CREATOR', 'Next button not ready, waiting longer...', 'warn', 'yellow') + await this.humanDelay(3000, 5000) + } + + // Ensure button is enabled + const isEnabled = await nextBtn.isEnabled() + if (!isEnabled) { + log(false, 'CREATOR', 'Waiting for Next button to be enabled...', 'warn') + await this.humanDelay(3000, 5000) + } + + // Get current URL before clicking + const urlBefore = this.page.url() + + await nextBtn.click() + log(false, 'CREATOR', `βœ… Clicked Next (${step})`, 'log', 'green') + + // CRITICAL: Wait for page to process the click + await this.humanDelay(3000, 5000) + + // CRITICAL: Wait for page to be stable after clicking + await this.waitForPageStable(`AFTER_NEXT_${step.toUpperCase()}`, 20000) + + // Verify navigation happened (if expected) + const urlAfter = this.page.url() + if (urlBefore !== urlAfter) { + log(false, 'CREATOR', `Navigation detected: ${urlBefore} β†’ ${urlAfter}`, 'log', 'cyan') + } + } + + private async fillPassword(): Promise { + log(false, 'CREATOR', 'Waiting for password page...', 'log') + + // Wait for password title to appear (language-independent) + await this.page.locator('h1[data-testid="title"]').first().waitFor({ timeout: 20000 }) + await this.humanDelay(1000, 2000) + + log(false, 'CREATOR', 'Generating strong password...', 'log') + const password = this.dataGenerator.generatePassword() + + // Find password input + const passwordInput = this.page.locator('input[type="password"]').first() + await passwordInput.waitFor({ timeout: 15000 }) + + // Clear and fill + await passwordInput.clear() + await this.humanDelay(500, 1000) + await passwordInput.fill(password) + await this.humanDelay(800, 2000) + + log(false, 'CREATOR', 'βœ… Password filled (hidden for security)', 'log', 'green') + + return password + } + + private async extractEmail(): Promise { + log(false, 'CREATOR', 'Extracting email from identity badge...', 'log') + + // Multiple selectors for identity badge (language-independent) + const badgeSelectors = [ + '#bannerText', + 'div[id="identityBadge"] div', + 'div[data-testid="identityBanner"] div', + 'div[class*="identityBanner"]', + 'span[class*="identityText"]' + ] + + for (const selector of badgeSelectors) { + try { + const badge = this.page.locator(selector).first() + await badge.waitFor({ timeout: 5000 }) + + const email = await badge.textContent() + + if (email && email.includes('@')) { + const cleanEmail = email.trim() + log(false, 'CREATOR', `βœ… Email extracted: ${cleanEmail}`, 'log', 'green') + return cleanEmail + } + } catch { + // Try next selector + continue + } + } + + log(false, 'CREATOR', 'Could not find identity badge (not critical)', 'warn') + return null + } + + private async fillBirthdate(): Promise<{ day: number; month: number; year: number } | null> { + log(false, 'CREATOR', 'Filling birthdate...', 'log') + + const birthdate = this.dataGenerator.generateBirthdate() + + try { + // Fill day dropdown - wait for the page to be ready + await this.humanDelay(1000, 1500) + + const dayButton = this.page.locator('button[name="BirthDay"], button#BirthDayDropdown').first() + await dayButton.waitFor({ timeout: 15000, state: 'visible' }) + + log(false, 'CREATOR', 'Clicking day dropdown...', 'log') + + // Use force:true in case label intercepts pointer events + await dayButton.click({ force: true }) + await this.humanDelay(1000, 1500) + + // Wait for dropdown menu to open and be visible + log(false, 'CREATOR', 'Waiting for day dropdown to open...', 'log') + const dayOptionsContainer = this.page.locator('div[role="listbox"], ul[role="listbox"]').first() + + try { + await dayOptionsContainer.waitFor({ timeout: 10000, state: 'visible' }) + log(false, 'CREATOR', 'βœ… Day dropdown opened', 'log', 'green') + } catch (error) { + log(false, 'CREATOR', 'Day dropdown did not open, retrying click...', 'warn', 'yellow') + await dayButton.click({ force: true }) + await this.humanDelay(1000, 1500) + await dayOptionsContainer.waitFor({ timeout: 10000, state: 'visible' }) + } + + // Select day from dropdown - the options appear as li/div with role="option" + log(false, 'CREATOR', `Selecting day: ${birthdate.day}`, 'log') + const dayOption = this.page.locator(`div[role="option"]:has-text("${birthdate.day}"), li[role="option"]:has-text("${birthdate.day}")`).first() + await dayOption.waitFor({ timeout: 5000, state: 'visible' }) + await dayOption.click() + await this.humanDelay(1000, 1500) + + // CRITICAL: Wait for day dropdown menu to fully close before opening month + log(false, 'CREATOR', 'Waiting for day dropdown to close...', 'log') + await this.humanDelay(1000, 1500) + + // Verify day dropdown is closed + const dayMenuClosed = await this.page.locator('div[role="listbox"], ul[role="listbox"]').isVisible().catch(() => false) + if (dayMenuClosed) { + log(false, 'CREATOR', 'Day menu still open, waiting more...', 'warn', 'yellow') + await this.humanDelay(1000, 2000) + } + + // Fill month dropdown + const monthButton = this.page.locator('button[name="BirthMonth"], button#BirthMonthDropdown').first() + await monthButton.waitFor({ timeout: 10000, state: 'visible' }) + + log(false, 'CREATOR', 'Clicking month dropdown...', 'log') + + // CRITICAL: Use force:true because label intercepts pointer events + await monthButton.click({ force: true }) + await this.humanDelay(1000, 1500) + + // Wait for month dropdown menu to open and be visible + log(false, 'CREATOR', 'Waiting for month dropdown to open...', 'log') + const monthOptionsContainer = this.page.locator('div[role="listbox"], ul[role="listbox"]').first() + + try { + await monthOptionsContainer.waitFor({ timeout: 10000, state: 'visible' }) + log(false, 'CREATOR', 'βœ… Month dropdown opened', 'log', 'green') + } catch (error) { + log(false, 'CREATOR', 'Month dropdown did not open, retrying click...', 'warn', 'yellow') + await monthButton.click({ force: true }) + await this.humanDelay(1000, 1500) + await monthOptionsContainer.waitFor({ timeout: 10000, state: 'visible' }) + } + + // Select month by data-value attribute or by position + log(false, 'CREATOR', `Selecting month: ${birthdate.month}`, 'log') + const monthOption = this.page.locator(`div[role="option"][data-value="${birthdate.month}"], li[role="option"][data-value="${birthdate.month}"]`).first() + + // Fallback: if data-value doesn't work, try by index (month - 1) + const monthVisible = await monthOption.isVisible().catch(() => false) + if (monthVisible) { + await monthOption.click() + log(false, 'CREATOR', 'βœ… Month selected by data-value', 'log', 'green') + } else { + log(false, 'CREATOR', `Fallback: selecting month by nth-child(${birthdate.month})`, 'warn', 'yellow') + const monthOptionByIndex = this.page.locator(`div[role="option"]:nth-child(${birthdate.month}), li[role="option"]:nth-child(${birthdate.month})`).first() + await monthOptionByIndex.click() + } + await this.humanDelay(1000, 1500) + + // Wait for month dropdown to close + log(false, 'CREATOR', 'Waiting for month dropdown to close...', 'log') + await this.humanDelay(1000, 1500) + + // Fill year input + const yearInput = this.page.locator('input[name="BirthYear"], input[type="number"]').first() + await yearInput.waitFor({ timeout: 10000, state: 'visible' }) + + log(false, 'CREATOR', `Filling year: ${birthdate.year}`, 'log') + await yearInput.clear() + await this.humanDelay(500, 1000) + await yearInput.fill(birthdate.year.toString()) + await this.humanDelay(800, 2000) + + log(false, 'CREATOR', `βœ… Birthdate filled: ${birthdate.day}/${birthdate.month}/${birthdate.year}`, 'log', 'green') + + return birthdate + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Error filling birthdate: ${msg}`, 'error') + return null + } + } + + private async fillNames(email: string): Promise<{ firstName: string; lastName: string } | null> { + log(false, 'CREATOR', 'Filling first and last name...', 'log') + + const names = this.dataGenerator.generateNames(email) + + try { + // Fill first name with multiple selector fallbacks + const firstNameSelectors = [ + 'input[id*="firstName"]', + 'input[name*="firstName"]', + 'input[id*="first"]', + 'input[name*="first"]', + 'input[aria-label*="First"]', + 'input[placeholder*="First"]' + ] + + let firstNameInput = null + for (const selector of firstNameSelectors) { + const input = this.page.locator(selector).first() + const visible = await input.isVisible().catch(() => false) + if (visible) { + firstNameInput = input + break + } + } + + if (!firstNameInput) { + log(false, 'CREATOR', 'Could not find first name input', 'error') + return null + } + + await firstNameInput.clear() + await this.humanDelay(500, 1000) + await firstNameInput.fill(names.firstName) + await this.humanDelay(800, 2000) + + // Fill last name with multiple selector fallbacks + const lastNameSelectors = [ + 'input[id*="lastName"]', + 'input[name*="lastName"]', + 'input[id*="last"]', + 'input[name*="last"]', + 'input[aria-label*="Last"]', + 'input[placeholder*="Last"]' + ] + + let lastNameInput = null + for (const selector of lastNameSelectors) { + const input = this.page.locator(selector).first() + const visible = await input.isVisible().catch(() => false) + if (visible) { + lastNameInput = input + break + } + } + + if (!lastNameInput) { + log(false, 'CREATOR', 'Could not find last name input', 'error') + return null + } + + await lastNameInput.clear() + await this.humanDelay(500, 1000) + await lastNameInput.fill(names.lastName) + await this.humanDelay(800, 2000) + + log(false, 'CREATOR', `βœ… Names filled: ${names.firstName} ${names.lastName}`, 'log', 'green') + + return names + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Error filling names: ${msg}`, 'error') + return null + } + } + + private async waitForCaptcha(): Promise { + try { + // Wait a bit to let the page load + await this.humanDelay(1500, 2500) + + // Check for CAPTCHA iframe (most reliable indicator) + const captchaIframe = this.page.locator('iframe[data-testid="humanCaptchaIframe"]').first() + const iframeVisible = await captchaIframe.isVisible().catch(() => false) + + if (iframeVisible) { + log(false, 'CREATOR', 'CAPTCHA detected via iframe', 'warn', 'yellow') + return true + } + + // Check multiple indicators for CAPTCHA + const captchaIndicators = [ + 'h1[data-testid="title"]', + 'div[id*="captcha"]', + 'div[class*="captcha"]', + 'div[id*="enforcement"]', + 'img[data-testid="accessibleImg"]' + ] + + for (const selector of captchaIndicators) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + const text = await element.textContent().catch(() => '') + + // Check for CAPTCHA-related keywords + if (text && ( + text.toLowerCase().includes('vΓ©rif') || + text.toLowerCase().includes('verify') || + text.toLowerCase().includes('human') || + text.toLowerCase().includes('humain') || + text.toLowerCase().includes('puzzle') || + text.toLowerCase().includes('captcha') || + text.toLowerCase().includes('prove you') + )) { + log(false, 'CREATOR', `CAPTCHA detected with text: ${text.substring(0, 50)}`, 'warn', 'yellow') + return true + } + } + } + + return false + } catch { + return false + } + } + + private async waitForCaptchaSolved(): Promise { + const maxWaitTime = 10 * 60 * 1000 // 10 minutes + const startTime = Date.now() + let lastLogTime = startTime + + while (Date.now() - startTime < maxWaitTime) { + try { + // Log progress every 30 seconds + if (Date.now() - lastLogTime > 30000) { + const elapsed = Math.floor((Date.now() - startTime) / 1000) + log(false, 'CREATOR', `Still waiting for CAPTCHA... (${elapsed}s elapsed)`, 'log', 'yellow') + lastLogTime = Date.now() + } + + // Check if CAPTCHA is still present + const captchaStillPresent = await this.waitForCaptcha() + + if (!captchaStillPresent) { + // CAPTCHA solved! But account creation may still be in progress + log(false, 'CREATOR', 'βœ… CAPTCHA solved! Waiting for account creation...', 'log', 'green') + + // CRITICAL: Wait for Microsoft to finish creating the account + await this.waitForAccountCreation() + + return + } + + // Wait before checking again + await this.page.waitForTimeout(2000) + + } catch { + // Error checking, assume CAPTCHA is solved + return + } + } + + throw new Error('CAPTCHA solving timeout - waited 10 minutes') + } + + private async handlePostCreationQuestions(): Promise { + log(false, 'CREATOR', 'Handling post-creation questions...', 'log', 'cyan') + + // CRITICAL: Ensure page is fully loaded and stable before proceeding + await this.waitForPageStable('POST_CREATION', 30000) + + // Additional safety delay + await this.humanDelay(3000, 5000) + + // CRITICAL: Handle passkey prompt - MUST REFUSE + await this.handlePasskeyPrompt() + + // Handle "Stay signed in?" (KMSI) prompt + const kmsiSelectors = [ + '[data-testid="kmsiVideo"]', + 'div:has-text("Stay signed in?")', + 'div:has-text("Rester connectΓ©")', + 'button[data-testid="primaryButton"]' + ] + + for (let i = 0; i < 3; i++) { + let found = false + + for (const selector of kmsiSelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', 'Stay signed in prompt detected', 'log', 'yellow') + + // Click "Yes" button + const yesButton = this.page.locator('button[data-testid="primaryButton"]').first() + const yesVisible = await yesButton.isVisible().catch(() => false) + + if (yesVisible) { + await yesButton.click() + await this.humanDelay(2000, 3000) + log(false, 'CREATOR', 'βœ… Accepted "Stay signed in"', 'log', 'green') + found = true + break + } + } + } + + if (!found) break + await this.humanDelay(1000, 2000) + } + + // Handle any other prompts (biometric, etc.) + const genericPrompts = [ + '[data-testid="biometricVideo"]', + 'button[id*="close"]', + 'button[aria-label*="Close"]' + ] + + for (const selector of genericPrompts) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Closing prompt: ${selector}`, 'log', 'yellow') + + // Try to close it + const closeButton = this.page.locator('button[data-testid="secondaryButton"], button[id*="close"]').first() + const closeVisible = await closeButton.isVisible().catch(() => false) + + if (closeVisible) { + await closeButton.click() + await this.humanDelay(1500, 2500) + log(false, 'CREATOR', 'βœ… Closed prompt', 'log', 'green') + } + } + } + + log(false, 'CREATOR', 'βœ… Post-creation questions handled', 'log', 'green') + } + + private async handlePasskeyPrompt(): Promise { + log(false, 'CREATOR', 'Checking for passkey setup prompt...', 'log', 'cyan') + + // CRITICAL: Wait longer for passkey prompt to appear + // Microsoft may show this after several seconds + await this.humanDelay(5000, 7000) + + // Ensure page is stable before checking + await this.waitForPageStable('PASSKEY_CHECK', 15000) + + // Multiple selectors for passkey prompt detection + const passkeyDetectionSelectors = [ + 'div:has-text("passkey")', + 'div:has-text("clΓ© d\'accΓ¨s")', + 'div:has-text("Set up a passkey")', + 'div:has-text("Configurer une clΓ©")', + '[data-testid*="passkey"]', + 'button:has-text("Skip")', + 'button:has-text("Not now")', + 'button:has-text("Ignorer")', + 'button:has-text("Plus tard")' + ] + + let passkeyPromptFound = false + + for (const selector of passkeyDetectionSelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + passkeyPromptFound = true + log(false, 'CREATOR', '⚠️ Passkey setup prompt detected - REFUSING', 'warn', 'yellow') + break + } + } + + if (!passkeyPromptFound) { + log(false, 'CREATOR', 'No passkey prompt detected', 'log', 'green') + return + } + + // Try to click refuse/skip buttons + const refuseButtonSelectors = [ + 'button:has-text("Skip")', + 'button:has-text("Not now")', + 'button:has-text("No")', + 'button:has-text("Cancel")', + 'button:has-text("Ignorer")', + 'button:has-text("Plus tard")', + 'button:has-text("Non")', + 'button:has-text("Annuler")', + 'button[data-testid="secondaryButton"]', + 'button[id*="cancel"]', + 'button[id*="skip"]' + ] + + for (const selector of refuseButtonSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Clicking refuse button: ${selector}`, 'log', 'cyan') + await button.click() + await this.humanDelay(2000, 3000) + log(false, 'CREATOR', 'βœ… Passkey setup REFUSED', 'log', 'green') + return + } + } + + log(false, 'CREATOR', '⚠️ Could not find refuse button for passkey prompt', 'warn', 'yellow') + } + + private async verifyAccountActive(): Promise { + log(false, 'CREATOR', 'Verifying account is active...', 'log', 'cyan') + + // CRITICAL: Ensure current page is stable before navigating + await this.waitForPageStable('PRE_VERIFICATION', 15000) + + // Additional delay before navigation + await this.humanDelay(3000, 5000) + + // Navigate to Bing Rewards + try { + log(false, 'CREATOR', 'Navigating to rewards.bing.com...', 'log', 'cyan') + + await this.page.goto('https://rewards.bing.com/', { + waitUntil: 'networkidle', + timeout: 60000 + }) + + // CRITICAL: Wait for page to be fully stable after navigation + await this.waitForPageStable('REWARDS_PAGE', 30000) + + // Additional safety delay + await this.humanDelay(5000, 7000) + + const currentUrl = this.page.url() + log(false, 'CREATOR', `Current URL: ${currentUrl}`, 'log', 'cyan') + + // CRITICAL: Verify we're actually on rewards page and logged in + if (!currentUrl.includes('rewards.bing.com')) { + if (currentUrl.includes('login.live.com')) { + log(false, 'CREATOR', '⚠️ Still on login page - account may not be fully activated', 'warn', 'yellow') + + // Wait longer and retry + await this.humanDelay(10000, 15000) + await this.page.goto('https://rewards.bing.com/', { + waitUntil: 'networkidle', + timeout: 60000 + }) + await this.waitForPageStable('REWARDS_RETRY', 30000) + } else { + log(false, 'CREATOR', `⚠️ Unexpected URL: ${currentUrl}`, 'warn', 'yellow') + } + } + + log(false, 'CREATOR', 'βœ… Successfully navigated to rewards.bing.com', 'log', 'green') + + // CRITICAL: Wait for user identity to load before declaring success + await this.humanDelay(5000, 7000) + + // CRITICAL: Verify user identity is loaded + log(false, 'CREATOR', 'Verifying user identity...', 'log', 'cyan') + + const identitySelectors = [ + '[data-bi-id="userIdentity"]', + '[id*="user"]', + 'button[aria-label*="Account"]', + '#id_n', // User dropdown + '.mee_header_profile' // Profile area + ] + + let identityVerified = false + for (const selector of identitySelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + const text = await element.textContent().catch(() => '') + if (text && text.trim().length > 0) { + log(false, 'CREATOR', `βœ… Verified identity: ${text.substring(0, 50)}`, 'log', 'green') + identityVerified = true + break + } + } + } + + if (!identityVerified) { + log(false, 'CREATOR', '⚠️ Could not verify user identity on page', 'warn', 'yellow') + + // Wait longer and check again + await this.humanDelay(5000, 7000) + + for (const selector of identitySelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', 'βœ… Identity verified on retry', 'log', 'green') + identityVerified = true + break + } + } + } + + if (identityVerified) { + log(false, 'CREATOR', 'βœ… Account is active and logged in!', 'log', 'green') + } else { + log(false, 'CREATOR', '⚠️ Account state uncertain - proceeding anyway', 'warn', 'yellow') + } + + // NOW handle popups and tour - AFTER confirming we're logged in + await this.humanDelay(3000, 5000) + await this.handleRewardsWelcomeTour() + await this.humanDelay(3000, 5000) + await this.handleRewardsPopups() + + // If we have a referral URL, ensure we join via it + if (this.referralUrl) { + await this.humanDelay(3000, 4000) + await this.ensureRewardsEnrollment() + } + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Warning: Could not verify account: ${msg}`, 'warn', 'yellow') + } + } + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Warning: Could not verify account: ${msg}`, 'warn', 'yellow') + } + } + + private async handleRewardsWelcomeTour(): Promise { + log(false, 'CREATOR', 'Checking for Microsoft Rewards welcome tour...', 'log', 'cyan') + + // CRITICAL: Ensure page is stable before checking for tour + await this.waitForPageStable('WELCOME_TOUR', 20000) + + // Additional delay for tour to appear + await this.humanDelay(5000, 7000) + + // Try to handle the welcome tour (multiple Next buttons) + const maxClicks = 5 + for (let i = 0; i < maxClicks; i++) { + // Check for welcome tour indicators + const welcomeIndicators = [ + 'img[src*="Get%20cool%20prizes"]', + 'img[alt*="Welcome to Microsoft Rewards"]', + 'div.welcome-tour', + 'a#fre-next-button', + 'a.welcome-tour-button.next-button', + 'a.next-button.c-call-to-action' + ] + + let tourFound = false + for (const selector of welcomeIndicators) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + tourFound = true + log(false, 'CREATOR', `Welcome tour detected (step ${i + 1})`, 'log', 'yellow') + break + } + } + + if (!tourFound) { + log(false, 'CREATOR', 'No more welcome tour steps', 'log', 'green') + break + } + + // Try to click Next button + const nextButtonSelectors = [ + 'a#fre-next-button', + 'a.welcome-tour-button.next-button', + 'a.next-button.c-call-to-action', + 'button:has-text("Next")', + 'a:has-text("Next")', + 'button:has-text("Suivant")', + 'a:has-text("Suivant")' + ] + + let clickedNext = false + for (const selector of nextButtonSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Clicking Next button: ${selector}`, 'log', 'cyan') + await button.click() + + // CRITICAL: Wait longer after clicking to let animation complete + await this.humanDelay(3000, 4000) + + clickedNext = true + log(false, 'CREATOR', `βœ… Clicked Next (step ${i + 1})`, 'log', 'green') + break + } + } + + if (!clickedNext) { + // Try "Pin and start earning" button (final step) + const pinButtonSelectors = [ + 'a#claim-button', + 'a:has-text("Pin and start earning")', + 'a:has-text("Γ‰pingler et commencer")', + 'a.welcome-tour-button[href*="pin=true"]' + ] + + for (const selector of pinButtonSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', 'Clicking "Pin and start earning" button', 'log', 'cyan') + await button.click() + await this.humanDelay(3000, 4000) + log(false, 'CREATOR', 'βœ… Clicked Pin button', 'log', 'green') + break + } + } + + break + } + + // Wait between steps to avoid spamming + await this.humanDelay(2000, 3000) + } + + log(false, 'CREATOR', 'βœ… Welcome tour handled', 'log', 'green') + } + + private async handleRewardsPopups(): Promise { + log(false, 'CREATOR', 'Checking for Microsoft Rewards popups...', 'log', 'cyan') + + // CRITICAL: Ensure page is stable before checking for popups + await this.waitForPageStable('REWARDS_POPUPS', 20000) + + // Wait longer for any popups to appear + await this.humanDelay(5000, 7000) + + // Handle ReferAndEarn popup + const referralPopupSelectors = [ + 'img[src*="ReferAndEarnPopUpImgUpdated"]', + 'div.dashboardPopUp', + 'a.dashboardPopUpPopUpSelectButton', + 'a#reward_pivot_earn' + ] + + let referralPopupFound = false + for (const selector of referralPopupSelectors) { + const element = this.page.locator(selector).first() + const visible = await element.isVisible().catch(() => false) + + if (visible) { + referralPopupFound = true + log(false, 'CREATOR', 'Referral popup detected', 'log', 'yellow') + break + } + } + + if (referralPopupFound) { + // Wait before clicking to ensure popup is fully loaded + await this.humanDelay(2000, 3000) + + // Click "Get started" button + const getStartedSelectors = [ + 'a.dashboardPopUpPopUpSelectButton', + 'a#reward_pivot_earn', + 'a:has-text("Get started")', + 'a:has-text("Commencer")', + 'button:has-text("Get started")', + 'button:has-text("Commencer")' + ] + + for (const selector of getStartedSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', 'Clicking "Get started" button', 'log', 'cyan') + await button.click() + await this.humanDelay(3000, 4000) + log(false, 'CREATOR', 'βœ… Clicked Get started', 'log', 'green') + break + } + } + } + + // Handle any other generic popups + const genericCloseSelectors = [ + 'button[aria-label*="Close"]', + 'button[aria-label*="Fermer"]', + 'button.close', + 'a.close' + ] + + for (const selector of genericCloseSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Closing popup with selector: ${selector}`, 'log', 'cyan') + await button.click() + await this.humanDelay(2000, 3000) + } + } + + log(false, 'CREATOR', 'βœ… Popups handled', 'log', 'green') + } + + private async ensureRewardsEnrollment(): Promise { + log(false, 'CREATOR', 'Ensuring Microsoft Rewards enrollment via referral URL...', 'log', 'cyan') + + if (!this.referralUrl) { + log(false, 'CREATOR', 'No referral URL provided, skipping enrollment check', 'warn', 'yellow') + return + } + + try { + // Navigate to referral URL + log(false, 'CREATOR', `Navigating to referral URL: ${this.referralUrl}`, 'log', 'cyan') + await this.page.goto(this.referralUrl, { + waitUntil: 'networkidle', + timeout: 60000 + }) + + // CRITICAL: Wait for page to be stable after navigation + await this.waitForPageStable('REFERRAL_ENROLLMENT', 30000) + + // Additional delay + await this.humanDelay(5000, 7000) + + // Look for "Join Microsoft Rewards" button + const joinButtonSelectors = [ + 'a#start-earning-rewards-link', + 'a.cta.learn-more-btn', + 'a[href*="createNewUser"]', + 'a:has-text("Join Microsoft Rewards")', + 'a:has-text("Rejoindre Microsoft Rewards")', + 'button:has-text("Join")', + 'button:has-text("Rejoindre")' + ] + + let joined = false + for (const selector of joinButtonSelectors) { + const button = this.page.locator(selector).first() + const visible = await button.isVisible().catch(() => false) + + if (visible) { + log(false, 'CREATOR', `Clicking "Join Microsoft Rewards" button: ${selector}`, 'log', 'cyan') + await button.click() + await this.humanDelay(3000, 5000) + log(false, 'CREATOR', 'βœ… Clicked Join button', 'log', 'green') + joined = true + break + } + } + + if (!joined) { + log(false, 'CREATOR', 'Join button not found - account may already be enrolled', 'log', 'yellow') + } + + // CRITICAL: Wait for enrollment to complete and page to stabilize + await this.waitForPageStable('POST_ENROLLMENT', 30000) + await this.humanDelay(5000, 7000) + + // Handle any popups after joining - with delays between + await this.humanDelay(3000, 5000) + await this.handleRewardsWelcomeTour() + await this.humanDelay(3000, 5000) + await this.handleRewardsPopups() + + log(false, 'CREATOR', 'βœ… Rewards enrollment completed', 'log', 'green') + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `Warning: Could not complete enrollment: ${msg}`, 'warn', 'yellow') + } + } + + private async saveAccount(account: CreatedAccount): Promise { + try { + const accountsDir = path.join(process.cwd(), 'accounts-created') + + // Ensure directory exists + if (!fs.existsSync(accountsDir)) { + log(false, 'CREATOR', 'Creating accounts-created directory...', 'log', 'cyan') + fs.mkdirSync(accountsDir, { recursive: true }) + } + + // Create a unique filename for THIS account using timestamp and email + const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\./g, '-') + const emailPrefix = (account.email.split('@')[0] || 'account').substring(0, 20) // First 20 chars of email + const filename = `account_${emailPrefix}_${timestamp}.jsonc` + const filepath = path.join(accountsDir, filename) + + log(false, 'CREATOR', `Saving account to NEW file: ${filename}`, 'log', 'cyan') + + // Create account data with metadata + const accountData = { + ...account, + savedAt: new Date().toISOString(), + filename: filename + } + + // Create output with comments + const output = `// Microsoft Rewards - Account Created +// Email: ${account.email} +// Created: ${account.createdAt} +// Saved: ${accountData.savedAt} + +${JSON.stringify(accountData, null, 2)}` + + // Write to NEW file (never overwrites existing files) + fs.writeFileSync(filepath, output, 'utf-8') + + // Verify the file was written correctly + if (fs.existsSync(filepath)) { + const verifySize = fs.statSync(filepath).size + log(false, 'CREATOR', `βœ… File written successfully (${verifySize} bytes)`, 'log', 'green') + + // Double-check we can read it back + const verifyContent = fs.readFileSync(filepath, 'utf-8') + const verifyJsonStartIndex = verifyContent.indexOf('{') + const verifyJsonEndIndex = verifyContent.lastIndexOf('}') + + if (verifyJsonStartIndex !== -1 && verifyJsonEndIndex !== -1) { + const verifyJsonContent = verifyContent.substring(verifyJsonStartIndex, verifyJsonEndIndex + 1) + const verifyAccount = JSON.parse(verifyJsonContent) + + if (verifyAccount.email === account.email) { + log(false, 'CREATOR', `βœ… Verification passed: Account ${account.email} saved correctly`, 'log', 'green') + } else { + log(false, 'CREATOR', '⚠️ Verification warning: Email mismatch', 'warn', 'yellow') + } + } + } else { + log(false, 'CREATOR', '❌ File verification failed - file does not exist!', 'error') + } + + log(false, 'CREATOR', `βœ… Account saved successfully to: ${filepath}`, 'log', 'green') + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + log(false, 'CREATOR', `❌ Error saving account: ${msg}`, 'error') + + // Try to save to a fallback file + try { + const fallbackPath = path.join(process.cwd(), `account-backup-${Date.now()}.jsonc`) + fs.writeFileSync(fallbackPath, JSON.stringify(account, null, 2), 'utf-8') + log(false, 'CREATOR', `⚠️ Account saved to fallback file: ${fallbackPath}`, 'warn', 'yellow') + } catch (fallbackError) { + const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError) + log(false, 'CREATOR', `❌ Failed to save fallback: ${fallbackMsg}`, 'error') + } + } + } + + async close(): Promise { + this.rl.close() + if (this.page && !this.page.isClosed()) { + await this.page.close() + } + } +} diff --git a/src/account-creation/DataGenerator.ts b/src/account-creation/DataGenerator.ts new file mode 100644 index 0000000..67100b2 --- /dev/null +++ b/src/account-creation/DataGenerator.ts @@ -0,0 +1,104 @@ +import { getRandomFirstName, getRandomLastName } from './nameDatabase' + +export class DataGenerator { + + generateEmail(customFirstName?: string, customLastName?: string): string { + const firstName = customFirstName || getRandomFirstName() + const lastName = customLastName || getRandomLastName() + + const cleanFirst = firstName.toLowerCase().replace(/[^a-z]/g, '') + const cleanLast = lastName.toLowerCase().replace(/[^a-z]/g, '') + + // More realistic patterns + const randomNum = Math.floor(Math.random() * 9999) + const randomYear = 1985 + Math.floor(Math.random() * 20) + + const patterns = [ + `${cleanFirst}.${cleanLast}`, + `${cleanFirst}${cleanLast}`, + `${cleanFirst}_${cleanLast}`, + `${cleanFirst}.${cleanLast}${randomNum}`, + `${cleanFirst}${randomNum}`, + `${cleanLast}${cleanFirst}`, + `${cleanFirst}.${cleanLast}${randomYear}`, + `${cleanFirst}${randomYear}` + ] + + const username = patterns[Math.floor(Math.random() * patterns.length)] + return `${username}@outlook.com` + } + + generatePassword(): string { + const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + const lowercase = 'abcdefghijklmnopqrstuvwxyz' + const numbers = '0123456789' + const symbols = '!@#$%^&*' + + let password = '' + + // Ensure at least one of each required type + password += uppercase[Math.floor(Math.random() * uppercase.length)] + password += lowercase[Math.floor(Math.random() * lowercase.length)] + password += numbers[Math.floor(Math.random() * numbers.length)] + password += symbols[Math.floor(Math.random() * symbols.length)] + + // Fill the rest (total length: 14-18 chars for better security) + const allChars = uppercase + lowercase + numbers + symbols + const targetLength = 14 + Math.floor(Math.random() * 5) + + for (let i = password.length; i < targetLength; i++) { + password += allChars[Math.floor(Math.random() * allChars.length)] + } + + // Shuffle to mix required characters + password = password.split('').sort(() => Math.random() - 0.5).join('') + + return password + } + + generateBirthdate(): { day: number; month: number; year: number } { + const currentYear = new Date().getFullYear() + + // Age between 20 and 45 years old (safer range) + const minAge = 20 + const maxAge = 45 + + const age = minAge + Math.floor(Math.random() * (maxAge - minAge + 1)) + const year = currentYear - age + + const month = 1 + Math.floor(Math.random() * 12) + const daysInMonth = new Date(year, month, 0).getDate() + const day = 1 + Math.floor(Math.random() * daysInMonth) + + return { day, month, year } + } + + generateNames(email: string): { firstName: string; lastName: string } { + const username = email.split('@')[0] || 'user' + + // Split on numbers, dots, underscores, hyphens + const parts = username.split(/[0-9._-]+/).filter(p => p.length > 1) + + if (parts.length >= 2) { + return { + firstName: this.capitalize(parts[0] || getRandomFirstName()), + lastName: this.capitalize(parts[1] || getRandomLastName()) + } + } else if (parts.length === 1 && parts[0]) { + return { + firstName: this.capitalize(parts[0]), + lastName: getRandomLastName() + } + } + + return { + firstName: getRandomFirstName(), + lastName: getRandomLastName() + } + } + + private capitalize(str: string): string { + if (!str || str.length === 0) return '' + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() + } +} diff --git a/src/account-creation/README.md b/src/account-creation/README.md new file mode 100644 index 0000000..dfb30d0 --- /dev/null +++ b/src/account-creation/README.md @@ -0,0 +1,285 @@ +# Account Creation Module + +Automatically create new Microsoft accounts with **realistic email generation**, **human-like delays**, **interactive mode**, and **referral link support**. + +## 🎯 Key Features + +### ✨ Stealth & Realism +- **200+ Name Database**: Generates natural emails like `james.wilson1995@outlook.com` +- **Human-like Delays**: Random 0.5-4s delays between actions to avoid bot detection +- **Interactive Mode**: Choose auto-generate or enter your own email +- **Microsoft Suggestions**: Automatically handles "email taken" with Microsoft's alternatives +- **Badge Reading**: Always reads final email from identity badge for accuracy + +### πŸ”§ Technical Features +- **Referral Support**: Create accounts from your referral links +- **Language-Independent**: CSS selectors work in any language +- **CAPTCHA Detection**: Pauses automatically, waits for human solving +- **Auto-Save**: Organized daily JSONC files in `accounts-created/` + +## πŸ“¦ Installation + +Already integrated - no additional setup needed! + +## πŸš€ Usage + +### Command Line + +```bash +# Without referral (standalone account) +npm run creator + +# With referral link (earns you referral credit) +npm run creator https://rewards.bing.com/welcome?rh=YOUR_CODE&ref=rafsrchae +``` + +### Interactive Flow + +When you run the creator: + +``` +=== Email Configuration === +Generate email automatically? (Y/n): +``` + +**Press Y or Enter**: Auto-generates realistic email +- Example: `sarah.martinez1998@hotmail.com` +- Uses 200+ names from database +- Multiple formats (firstname.lastname, firstnamelastname, etc.) + +**Press n**: Manual email input +- You type the email you want +- Example: `mycoolemail@outlook.com` + +## πŸ“§ Email Generation + +### Auto-Generation System + +The system creates **realistic, human-like emails**: + +```javascript +// Old (obvious bot pattern): +user1730970000abc@outlook.com ❌ + +// New (looks like real person): +james.wilson@outlook.com βœ… +emily.brown95@hotmail.com βœ… +alex_taylor@outlook.fr βœ… +michael.garcia1998@outlook.com βœ… +``` + +### Name Database + +- **150+ First Names**: Male, female, gender-neutral +- **90+ Last Names**: Common surnames worldwide +- **Smart Formatting**: Varies patterns to look natural + +### Email Formats + +The system randomly uses these patterns: +- `firstname.lastname@domain.com` +- `firstnamelastname@domain.com` +- `firstname_lastname@domain.com` +- `firstnamelastname95@domain.com` (random number 0-99) +- `firstname.lastname1995@domain.com` (birth year style) + +### Domains + +Randomly selects from: +- `outlook.com` +- `hotmail.com` +- `outlook.fr` + +## 🎭 Human-like Delays + +All actions have **random delays** to mimic human behavior: + +| Action | Delay Range | +|--------|-------------| +| After navigation | 1.5-3s | +| After button click | 2-4s | +| After dropdown select | 0.8-1.5s | +| After text input | 0.8-2s | +| Waiting for page load | 2-4s | + +This prevents Microsoft's bot detection from flagging your accounts. + +## πŸ”„ Microsoft Suggestions Handling + +**Problem**: Email already exists +**Microsoft's Response**: Shows alternative suggestions (e.g., `john.smith247@outlook.com`) + +**How the system handles it**: +1. βœ… Detects error message automatically +2. βœ… Finds suggestion toolbar +3. βœ… Clicks first suggestion +4. βœ… Reads final email from identity badge +5. βœ… Saves correct email to file + +**Example Flow**: +``` +You input: john.smith@outlook.com +Microsoft: ❌ Email taken +Microsoft: πŸ’‘ Suggestions: john.smith247@outlook.com, john.smith89@hotmail.com +System: βœ… Clicks first suggestion +Identity Badge: john.smith247@outlook.com +Saved Account: john.smith247@outlook.com ← Correct! +``` + +## πŸ”§ Complete Process Flow + +1. **Navigation** + - With referral: Goes to your referral URL β†’ Clicks "Join Microsoft Rewards" + - Without referral: Goes directly to `https://login.live.com/` + +2. **Email Configuration** (Interactive) + - Asks: Auto-generate or manual? + - Auto: Generates realistic email from name database + - Manual: You type the email + +3. **Email Submission** + - Fills email with human delays + - Clicks Next button + - Checks for "email taken" error + +4. **Suggestion Handling** (if needed) + - Detects error automatically + - Clicks Microsoft's first suggestion + - Continues smoothly + +5. **Identity Badge Reading** + - Reads final email from badge + - Ensures accuracy (especially after suggestions) + +6. **Password Generation** + - 12-16 characters + - Uppercase, lowercase, numbers, symbols + - Meets all Microsoft requirements + +7. **Birthdate** + - Random age: 18-50 years old + - Realistic distribution + +8. **Names** + - Extracted from email OR + - Generated from name database + - Capitalized properly + +9. **CAPTCHA Detection** + - Automatically detects CAPTCHA page + - Pauses and waits for human solving + - Up to 10 minutes timeout + - Logs progress every 30 seconds + +10. **Save Account** + - Saves to `accounts-created/created_accounts_YYYY-MM-DD.jsonc` + - Daily files for organization + - All details preserved + +## πŸ“„ Output Format + +```jsonc +// accounts-created/created_accounts_2025-01-09.jsonc +[ + { + "email": "james.wilson1995@outlook.com", + "password": "Xyz789!@#AbcDef", + "birthdate": { + "day": 17, + "month": 5, + "year": 1995 + }, + "firstName": "James", + "lastName": "Wilson", + "createdAt": "2025-01-09T10:30:00.000Z", + "referralUrl": "https://rewards.bing.com/welcome?rh=YOUR_CODE&ref=rafsrchae" + } +] +``` + +## πŸ“‚ File Structure + +``` +src/account-creation/ +β”œβ”€β”€ AccountCreator.ts # Main orchestration with delays & interaction +β”œβ”€β”€ DataGenerator.ts # Generates realistic data +β”œβ”€β”€ nameDatabase.ts # 200+ names for email generation +β”œβ”€β”€ cli.ts # Command-line interface with banner +β”œβ”€β”€ types.ts # TypeScript interfaces +└── README.md # This file +``` + +## πŸ” Technical Selectors (Language-Independent) + +| Element | Selector | +|---------|----------| +| Create Account | `span[role="button"].fui-Link, a[id*="signup"]` | +| Email Input | `input[type="email"]` | +| Password Input | `input[type="password"]` | +| Next Button | `button[data-testid="primaryButton"], button[type="submit"]` | +| Birth Day | `button[name="BirthDay"]` | +| Birth Month | `button[name="BirthMonth"]` | +| Birth Year | `input[name="BirthYear"]` | +| First Name | `input[id*="firstName"]` | +| Last Name | `input[id*="lastName"]` | +| Identity Badge | `#bannerText, div[data-testid="identityBanner"]` | +| Error Message | `div[id*="Error"], div[class*="error"]` | +| Suggestions | `div[role="toolbar"][data-testid="suggestions"]` | +| CAPTCHA Title | `h1[data-testid="title"]` | + +## ⚠️ Important Notes + +- **Browser stays open** during CAPTCHA - intentional (human solving required) +- **No CAPTCHA automation** - Microsoft detects and bans bots +- **Referral URL must be full URL** starting with `https://` +- **Multiple runs** append to same daily file +- **Badge reading is critical** - final email may differ from input (suggestions) +- **Human delays are mandatory** - prevents bot detection + +## 🎯 Why This Approach? + +### Old System (Bot-Like) +``` +❌ Email: user1730970000abc@outlook.com (obvious timestamp) +❌ Speed: Instant form filling (< 1 second) +❌ Errors: Didn't handle email-taken scenarios +❌ Badge: Ignored identity badge (wrong email saved) +``` + +### New System (Human-Like) +``` +βœ… Email: james.wilson1995@outlook.com (looks real) +βœ… Speed: 0.5-4s delays between actions (natural) +βœ… Errors: Handles suggestions automatically +βœ… Badge: Always reads final email (accurate) +βœ… Choice: User can choose auto or manual +``` + +## πŸ“Š Success Tips + +1. **Use auto-generate** for fastest creation +2. **Use manual mode** if you have specific email format requirements +3. **Let the script handle suggestions** - don't worry about "email taken" errors +4. **Solve CAPTCHA within 10 minutes** when prompted +5. **Check accounts-created/ folder** for all saved accounts + +## πŸ› Troubleshooting + +**Q: Email generation too fast?** +A: System uses 0.8-2s delays after each input - looks human. + +**Q: Email already taken?** +A: System automatically clicks Microsoft's suggestion and reads from badge. + +**Q: Want specific email format?** +A: Press 'n' when asked "Generate automatically?" and type your email. + +**Q: CAPTCHA timeout?** +A: You have 10 minutes to solve it. If timeout, run script again. + +**Q: Where are accounts saved?** +A: `accounts-created/created_accounts_YYYY-MM-DD.jsonc` (auto-created folder). + +--- + +**Made with ❀️ for Microsoft Rewards automation** diff --git a/src/account-creation/cli.ts b/src/account-creation/cli.ts new file mode 100644 index 0000000..719ddd9 --- /dev/null +++ b/src/account-creation/cli.ts @@ -0,0 +1,109 @@ +import Browser from '../browser/Browser' +import { AccountCreator } from './AccountCreator' +import { log } from '../util/Logger' +import { MicrosoftRewardsBot } from '../index' + +async function main() { + // Get referral URL from command line args + const args = process.argv.slice(2) + const referralUrl = args[0] // Optional referral URL + + // Validate URL format if provided + if (referralUrl && !referralUrl.startsWith('http')) { + log(false, 'CREATOR-CLI', '❌ Invalid URL format', 'error') + log(false, 'CREATOR-CLI', 'Usage: npm run creator [referralUrl]', 'log') + log(false, 'CREATOR-CLI', 'Example: npm run creator https://rewards.bing.com/welcome?rh=E3DCB441&ref=rafsrchae', 'log', 'cyan') + process.exit(1) + } + + // Banner + console.log('\n' + '='.repeat(60)) + log(false, 'CREATOR-CLI', 'πŸš€ Microsoft Account Creator', 'log', 'cyan') + console.log('='.repeat(60) + '\n') + + if (referralUrl) { + log(false, 'CREATOR-CLI', `βœ… Using referral URL: ${referralUrl}`, 'log', 'green') + } else { + log(false, 'CREATOR-CLI', '⚠️ No referral URL provided - account will NOT be linked to rewards', 'warn', 'yellow') + } + + // Create a temporary bot instance to access browser creation + const bot = new MicrosoftRewardsBot(false) + const browserFactory = new Browser(bot) + + try { + // Create browser (non-headless for user interaction with CAPTCHA) + log(false, 'CREATOR-CLI', 'Opening browser (required for CAPTCHA solving)...', 'log') + + // Create empty proxy config (no proxy for account creation) + const emptyProxy = { + proxyAxios: false, + url: '', + port: 0, + password: '', + username: '' + } + + const browserContext = await browserFactory.createBrowser(emptyProxy, 'account-creator') + + log(false, 'CREATOR-CLI', 'βœ… Browser opened successfully', 'log', 'green') + + // Create account + const creator = new AccountCreator(referralUrl) + const result = await creator.create(browserContext) + + if (result) { + // Success banner + console.log('\n' + '='.repeat(60)) + log(false, 'CREATOR-CLI', 'βœ… ACCOUNT CREATED SUCCESSFULLY!', 'log', 'green') + console.log('='.repeat(60)) + + // Display account details + log(false, 'CREATOR-CLI', `πŸ“§ Email: ${result.email}`, 'log', 'cyan') + log(false, 'CREATOR-CLI', `πŸ” Password: ${result.password}`, 'log', 'cyan') + log(false, 'CREATOR-CLI', `πŸ‘€ Name: ${result.firstName} ${result.lastName}`, 'log', 'cyan') + log(false, 'CREATOR-CLI', `πŸŽ‚ Birthdate: ${result.birthdate.day}/${result.birthdate.month}/${result.birthdate.year}`, 'log', 'cyan') + + if (result.referralUrl) { + log(false, 'CREATOR-CLI', 'πŸ”— Referral: Linked', 'log', 'green') + } + + console.log('='.repeat(60)) + log(false, 'CREATOR-CLI', 'πŸ’Ύ Account details saved to accounts-created/ directory', 'log', 'green') + console.log('='.repeat(60) + '\n') + + // Keep browser open - don't close + log(false, 'CREATOR-CLI', 'βœ… Account creation complete! Browser will remain open.', 'log', 'green') + log(false, 'CREATOR-CLI', 'You can now use the account or close the browser manually.', 'log', 'cyan') + log(false, 'CREATOR-CLI', 'Press Ctrl+C to exit the script.', 'log', 'yellow') + + // Keep process alive indefinitely + await new Promise(() => {}) // Never resolves + } else { + // Failure + console.log('\n' + '='.repeat(60)) + log(false, 'CREATOR-CLI', '❌ ACCOUNT CREATION FAILED', 'error') + console.log('='.repeat(60) + '\n') + + await browserContext.close() + process.exit(1) + } + + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + console.log('\n' + '='.repeat(60)) + log(false, 'CREATOR-CLI', `❌ Fatal error: ${msg}`, 'error') + console.log('='.repeat(60) + '\n') + process.exit(1) + } +} + +// Run if executed directly +if (require.main === module) { + main().catch(error => { + log(false, 'CREATOR-CLI', `Unhandled error: ${error}`, 'error') + process.exit(1) + }) +} + +export { main as createAccountCLI } diff --git a/src/account-creation/nameDatabase.ts b/src/account-creation/nameDatabase.ts new file mode 100644 index 0000000..5108830 --- /dev/null +++ b/src/account-creation/nameDatabase.ts @@ -0,0 +1,46 @@ +// Realistic name database for account creation +export const NAME_DATABASE = { + firstNames: { + male: [ + 'James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas', 'Charles', + 'Daniel', 'Matthew', 'Anthony', 'Mark', 'Donald', 'Steven', 'Paul', 'Andrew', 'Joshua', 'Kenneth', + 'Kevin', 'Brian', 'George', 'Timothy', 'Ronald', 'Edward', 'Jason', 'Jeffrey', 'Ryan', 'Jacob', + 'Nicolas', 'Lucas', 'Nathan', 'Benjamin', 'Samuel', 'Alexander', 'Christopher', 'Dylan', 'Logan', + 'Ethan', 'Mason', 'Liam', 'Noah', 'Oliver', 'Elijah', 'Aiden', 'Jackson', 'Sebastian', 'Jack' + ], + female: [ + 'Mary', 'Patricia', 'Jennifer', 'Linda', 'Barbara', 'Elizabeth', 'Jessica', 'Susan', 'Sarah', 'Karen', + 'Lisa', 'Nancy', 'Betty', 'Margaret', 'Sandra', 'Ashley', 'Kimberly', 'Emily', 'Donna', 'Michelle', + 'Carol', 'Amanda', 'Dorothy', 'Melissa', 'Deborah', 'Stephanie', 'Rebecca', 'Sharon', 'Laura', 'Cynthia', + 'Emma', 'Olivia', 'Ava', 'Isabella', 'Sophia', 'Mia', 'Charlotte', 'Amelia', 'Harper', 'Evelyn', + 'Abigail', 'Emily', 'Madison', 'Ella', 'Scarlett', 'Grace', 'Chloe', 'Victoria', 'Riley', 'Aria' + ], + neutral: [ + 'Alex', 'Jordan', 'Taylor', 'Morgan', 'Casey', 'Riley', 'Avery', 'Jamie', 'Quinn', 'Reese', + 'Skylar', 'Cameron', 'Drew', 'Blake', 'Sage', 'River', 'Phoenix', 'Charlie', 'Dakota', 'Rowan' + ] + }, + lastNames: [ + 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', + 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin', + 'Lee', 'Perez', 'Thompson', 'White', 'Harris', 'Sanchez', 'Clark', 'Ramirez', 'Lewis', 'Robinson', + 'Walker', 'Young', 'Allen', 'King', 'Wright', 'Scott', 'Torres', 'Nguyen', 'Hill', 'Flores', + 'Green', 'Adams', 'Nelson', 'Baker', 'Hall', 'Rivera', 'Campbell', 'Mitchell', 'Carter', 'Roberts', + 'Turner', 'Phillips', 'Evans', 'Collins', 'Stewart', 'Morris', 'Rogers', 'Reed', 'Cook', 'Morgan', + 'Bell', 'Murphy', 'Bailey', 'Cooper', 'Richardson', 'Cox', 'Howard', 'Ward', 'Peterson', 'Gray', + 'James', 'Watson', 'Brooks', 'Kelly', 'Sanders', 'Price', 'Bennett', 'Wood', 'Barnes', 'Ross', + 'Henderson', 'Coleman', 'Jenkins', 'Perry', 'Powell', 'Long', 'Patterson', 'Hughes', 'Flores', 'Washington' + ] +} + +export function getRandomFirstName(): string { + const categories = ['male', 'female', 'neutral'] + const category = categories[Math.floor(Math.random() * categories.length)] as keyof typeof NAME_DATABASE.firstNames + const names = NAME_DATABASE.firstNames[category] + return names[Math.floor(Math.random() * names.length)] || 'Alex' +} + +export function getRandomLastName(): string { + const names = NAME_DATABASE.lastNames + return names[Math.floor(Math.random() * names.length)] || 'Smith' +} diff --git a/src/account-creation/types.ts b/src/account-creation/types.ts new file mode 100644 index 0000000..ac328a8 --- /dev/null +++ b/src/account-creation/types.ts @@ -0,0 +1,14 @@ +export interface CreatedAccount { + email: string + password: string + birthdate: { + day: number + month: number + year: number + } + firstName: string + lastName: string + createdAt: string + referralUrl?: string + notes?: string +}