From ab32ef4cc280b72fa4fd6250e5c05ad526c65b22 Mon Sep 17 00:00:00 2001 From: Matt Jackson Date: Sat, 7 Mar 2026 23:39:13 +0000 Subject: [PATCH] Fix login flow: adaptive page reading, keyboard.type, session cleanup - Adaptive login loop reads page state each step and responds accordingly - Switched from fill() to keyboard.type() for anti-detection - Navigate to /compose/post for posting instead of sidebar button - Clean up stale browser sessions before creating new ones - Fixed +1 country code for phone verification Co-Authored-By: Claude Opus 4.6 --- bot.js | 103 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/bot.js b/bot.js index 138db45..24c3f34 100644 --- a/bot.js +++ b/bot.js @@ -121,7 +121,8 @@ async function login(kernel, sessionId, account) { const input = page.locator('input[autocomplete="username"]'); await input.waitFor({ state: 'visible', timeout: 10000 }); await input.click(); - await input.fill(${xAcct}); + await page.waitForTimeout(300); + await page.keyboard.type(${xAcct}, { delay: 50 }); await page.waitForTimeout(500); const nextBtn = page.locator('[role="button"]').filter({ hasText: 'Next' }); await nextBtn.click(); @@ -130,61 +131,83 @@ async function login(kernel, sessionId, account) { console.log("Username:", r1.success ? "ok" : r1.error); if (!r1.success) throw new Error("Username failed: " + r1.error); - let pageState = await readPage(kernel, sessionId); - console.log("After username:", JSON.stringify(pageState, null, 2)); + // Adaptive login flow — keep reading the page and responding + for (let step = 0; step < 5; step++) { + const pageState = await readPage(kernel, sessionId); + const pageText = pageState.texts.join(" ").toLowerCase(); + const inputTypes = pageState.inputs.map((i) => i.type); + console.log(`Step ${step}: url=${pageState.url}, inputs=[${inputTypes}], text=${pageText.slice(0, 200)}`); - const hasPassword = pageState.inputs.some((i) => i.type === "password"); - const pageText = pageState.texts.join(" ").toLowerCase(); - - if (!hasPassword && (pageText.includes("unusual login") || pageText.includes("verify") || pageText.includes("confirm"))) { - let verifyValue; - if (pageText.includes("phone number or email")) { - verifyValue = xPhone; - } else if (pageText.includes("email")) { - verifyValue = xEmail; - } else { - verifyValue = xPhone; + // Password field visible — we're ready to enter password + if (inputTypes.includes("password")) { + console.log("Password field found"); + break; + } + + // Determine what to type based on what the page is asking + let value; + if (pageText.includes("phone") && pageText.includes("email")) { + value = xPhone; + console.log("Page asks for phone or email — using phone"); + } else if (pageText.includes("phone")) { + value = xPhone; + console.log("Page asks for phone"); + } else if (pageText.includes("email")) { + value = xEmail; + console.log("Page asks for email"); + } else if (pageText.includes("username")) { + value = xAcct; + console.log("Page asks for username"); + } else { + console.log("Unknown page state, trying phone"); + value = xPhone; } - console.log("Verification required"); const rv = await exec(kernel, sessionId, ` - const input = page.locator('input[type="text"]'); - await input.waitFor({ state: 'visible', timeout: 10000 }); + const inputs = await page.locator('input:visible').all(); + const input = inputs[inputs.length - 1]; await input.click(); - await input.fill(${verifyValue}); + await input.clear(); + await page.waitForTimeout(300); + await page.keyboard.type(${value}, { delay: 50 }); await page.waitForTimeout(500); - const nextBtn = page.locator('[role="button"]').filter({ hasText: 'Next' }); - await nextBtn.click(); + const nextBtn = page.locator('[role="button"]').filter({ hasText: /Next|Log in|Continue/i }); + if (await nextBtn.count() > 0) { + await nextBtn.first().click(); + } await page.waitForTimeout(5000); `, 30); - if (!rv.success) throw new Error("Verification failed: " + rv.error); + if (!rv.success) throw new Error(`Step ${step} failed: ` + rv.error); + } - pageState = await readPage(kernel, sessionId); + const finalState = await readPage(kernel, sessionId); + if (!finalState.inputs.some((i) => i.type === "password")) { + console.log("Final state:", JSON.stringify(finalState, null, 2)); + throw new Error("Never reached password field"); } const r2 = await exec(kernel, sessionId, ` const input = page.locator('input[type="password"]'); await input.waitFor({ state: 'visible', timeout: 10000 }); await input.click(); - await input.fill(${xPw}); + await page.waitForTimeout(500); + await page.keyboard.type(${xPw}, { delay: 50 }); await page.waitForTimeout(500); const loginBtn = page.getByTestId('LoginForm_Login_Button'); await loginBtn.click(); - await page.waitForTimeout(5000); - `, 30); + await page.waitForTimeout(8000); + return page.url(); + `, 45); if (!r2.success) throw new Error("Password failed: " + r2.error); - pageState = await readPage(kernel, sessionId); - console.log("Logged in:", pageState.url); - return pageState.url; + console.log("Logged in:", r2.result); + return r2.result; } async function postTweet(kernel, sessionId, tweetText) { const composeResult = await exec(kernel, sessionId, ` - const composeBtn = page.getByTestId('SideNav_NewTweet_Button'); - await composeBtn.waitFor({ state: 'visible', timeout: 10000 }); - await composeBtn.click(); - await page.waitForTimeout(2000); + await page.goto('https://x.com/compose/post', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.waitForTimeout(3000); const editor = page.getByRole('textbox'); await editor.waitFor({ state: 'visible', timeout: 10000 }); return 'ready'; @@ -228,14 +251,26 @@ async function main() { } const kernel = new Kernel({ apiKey: process.env.KERNEL }); + + // Clean up any stale sessions + try { + const sessions = await kernel.browsers.list(); + if (sessions.length > 0) { + console.log(`Cleaning up ${sessions.length} stale session(s)...`); + for (const s of sessions) { + try { await kernel.browsers.delete(s.session_id); } catch {} + } + } + } catch {} + const browser = await kernel.browsers.create({ stealth: true }); const sessionId = browser.session_id; console.log(`[${promptName}] Session: ${sessionId}`); try { const url = await login(kernel, sessionId, promptConfig.account); - if (!url.includes("x.com/home")) { - throw new Error("Login didn't reach home page: " + url); + if (!url.includes("x.com")) { + throw new Error("Login failed: " + url); } const history = loadHistory(promptName);