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 <noreply@anthropic.com>
This commit is contained in:
103
bot.js
103
bot.js
@@ -121,7 +121,8 @@ async function login(kernel, sessionId, account) {
|
|||||||
const input = page.locator('input[autocomplete="username"]');
|
const input = page.locator('input[autocomplete="username"]');
|
||||||
await input.waitFor({ state: 'visible', timeout: 10000 });
|
await input.waitFor({ state: 'visible', timeout: 10000 });
|
||||||
await input.click();
|
await input.click();
|
||||||
await input.fill(${xAcct});
|
await page.waitForTimeout(300);
|
||||||
|
await page.keyboard.type(${xAcct}, { delay: 50 });
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
const nextBtn = page.locator('[role="button"]').filter({ hasText: 'Next' });
|
const nextBtn = page.locator('[role="button"]').filter({ hasText: 'Next' });
|
||||||
await nextBtn.click();
|
await nextBtn.click();
|
||||||
@@ -130,61 +131,83 @@ async function login(kernel, sessionId, account) {
|
|||||||
console.log("Username:", r1.success ? "ok" : r1.error);
|
console.log("Username:", r1.success ? "ok" : r1.error);
|
||||||
if (!r1.success) throw new Error("Username failed: " + r1.error);
|
if (!r1.success) throw new Error("Username failed: " + r1.error);
|
||||||
|
|
||||||
let pageState = await readPage(kernel, sessionId);
|
// Adaptive login flow — keep reading the page and responding
|
||||||
console.log("After username:", JSON.stringify(pageState, null, 2));
|
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");
|
// Password field visible — we're ready to enter password
|
||||||
const pageText = pageState.texts.join(" ").toLowerCase();
|
if (inputTypes.includes("password")) {
|
||||||
|
console.log("Password field found");
|
||||||
if (!hasPassword && (pageText.includes("unusual login") || pageText.includes("verify") || pageText.includes("confirm"))) {
|
break;
|
||||||
let verifyValue;
|
}
|
||||||
if (pageText.includes("phone number or email")) {
|
|
||||||
verifyValue = xPhone;
|
// Determine what to type based on what the page is asking
|
||||||
} else if (pageText.includes("email")) {
|
let value;
|
||||||
verifyValue = xEmail;
|
if (pageText.includes("phone") && pageText.includes("email")) {
|
||||||
} else {
|
value = xPhone;
|
||||||
verifyValue = 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 rv = await exec(kernel, sessionId, `
|
||||||
const input = page.locator('input[type="text"]');
|
const inputs = await page.locator('input:visible').all();
|
||||||
await input.waitFor({ state: 'visible', timeout: 10000 });
|
const input = inputs[inputs.length - 1];
|
||||||
await input.click();
|
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);
|
await page.waitForTimeout(500);
|
||||||
const nextBtn = page.locator('[role="button"]').filter({ hasText: 'Next' });
|
const nextBtn = page.locator('[role="button"]').filter({ hasText: /Next|Log in|Continue/i });
|
||||||
await nextBtn.click();
|
if (await nextBtn.count() > 0) {
|
||||||
|
await nextBtn.first().click();
|
||||||
|
}
|
||||||
await page.waitForTimeout(5000);
|
await page.waitForTimeout(5000);
|
||||||
`, 30);
|
`, 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 r2 = await exec(kernel, sessionId, `
|
||||||
const input = page.locator('input[type="password"]');
|
const input = page.locator('input[type="password"]');
|
||||||
await input.waitFor({ state: 'visible', timeout: 10000 });
|
await input.waitFor({ state: 'visible', timeout: 10000 });
|
||||||
await input.click();
|
await input.click();
|
||||||
await input.fill(${xPw});
|
await page.waitForTimeout(500);
|
||||||
|
await page.keyboard.type(${xPw}, { delay: 50 });
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
const loginBtn = page.getByTestId('LoginForm_Login_Button');
|
const loginBtn = page.getByTestId('LoginForm_Login_Button');
|
||||||
await loginBtn.click();
|
await loginBtn.click();
|
||||||
await page.waitForTimeout(5000);
|
await page.waitForTimeout(8000);
|
||||||
`, 30);
|
return page.url();
|
||||||
|
`, 45);
|
||||||
if (!r2.success) throw new Error("Password failed: " + r2.error);
|
if (!r2.success) throw new Error("Password failed: " + r2.error);
|
||||||
|
|
||||||
pageState = await readPage(kernel, sessionId);
|
console.log("Logged in:", r2.result);
|
||||||
console.log("Logged in:", pageState.url);
|
return r2.result;
|
||||||
return pageState.url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postTweet(kernel, sessionId, tweetText) {
|
async function postTweet(kernel, sessionId, tweetText) {
|
||||||
const composeResult = await exec(kernel, sessionId, `
|
const composeResult = await exec(kernel, sessionId, `
|
||||||
const composeBtn = page.getByTestId('SideNav_NewTweet_Button');
|
await page.goto('https://x.com/compose/post', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||||
await composeBtn.waitFor({ state: 'visible', timeout: 10000 });
|
await page.waitForTimeout(3000);
|
||||||
await composeBtn.click();
|
|
||||||
await page.waitForTimeout(2000);
|
|
||||||
const editor = page.getByRole('textbox');
|
const editor = page.getByRole('textbox');
|
||||||
await editor.waitFor({ state: 'visible', timeout: 10000 });
|
await editor.waitFor({ state: 'visible', timeout: 10000 });
|
||||||
return 'ready';
|
return 'ready';
|
||||||
@@ -228,14 +251,26 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const kernel = new Kernel({ apiKey: process.env.KERNEL });
|
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 browser = await kernel.browsers.create({ stealth: true });
|
||||||
const sessionId = browser.session_id;
|
const sessionId = browser.session_id;
|
||||||
console.log(`[${promptName}] Session: ${sessionId}`);
|
console.log(`[${promptName}] Session: ${sessionId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = await login(kernel, sessionId, promptConfig.account);
|
const url = await login(kernel, sessionId, promptConfig.account);
|
||||||
if (!url.includes("x.com/home")) {
|
if (!url.includes("x.com")) {
|
||||||
throw new Error("Login didn't reach home page: " + url);
|
throw new Error("Login failed: " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const history = loadHistory(promptName);
|
const history = loadHistory(promptName);
|
||||||
|
|||||||
Reference in New Issue
Block a user