feat: add Auto Run mode for full automated flow

- Background: add step completion waiting (promise-based), autoRun/resumeAutoRun
  chains steps 1→2 (pause for email) → 3→4→5→6→7→8→9 automatically
- Side Panel: add "Auto Run" button and "Continue Auto" bar for email pause point
- signup-page.js: add step 8 handler to click "继续" on OAuth consent page
- Fix all steps to reportComplete before form submit (prevents connection loss on navigation)
- qq-mail.js: fallback after 3 attempts to first matching email
- vps-panel.js: step 9 waits for "认证成功!" status badge
This commit is contained in:
unknown
2026-04-05 09:42:17 +08:00
parent 887709e824
commit 1c1e589bdc
7 changed files with 743 additions and 431 deletions
+100 -137
View File
@@ -1,5 +1,27 @@
// content/vps-panel.js — Content script for VPS panel (steps 1, 9)
// Injected on: http://154.26.182.181:8317/*
//
// Actual DOM structure (after login click):
// <div class="card">
// <div class="card-header">
// <span class="OAuthPage-module__cardTitle___yFaP0">Codex OAuth</span>
// <button class="btn btn-primary"><span>登录</span></button>
// </div>
// <div class="OAuthPage-module__cardContent___1sXLA">
// <div class="OAuthPage-module__authUrlBox___Iu1d4">
// <div class="OAuthPage-module__authUrlLabel___mYFJB">授权链接:</div>
// <div class="OAuthPage-module__authUrlValue___axvUJ">https://auth.openai.com/...</div>
// <div class="OAuthPage-module__authUrlActions___venPj">
// <button class="btn btn-secondary btn-sm"><span>复制链接</span></button>
// <button class="btn btn-secondary btn-sm"><span>打开链接</span></button>
// </div>
// </div>
// <div class="OAuthPage-module__callbackSection___8kA31">
// <input class="input" placeholder="http://localhost:1455/auth/callback?code=...&state=...">
// <button class="btn btn-secondary btn-sm"><span>提交回调 URL</span></button>
// </div>
// </div>
// </div>
console.log('[MultiPage:vps-panel] Content script loaded on', location.href);
@@ -30,178 +52,119 @@ async function handleStep(step, payload) {
// ============================================================
async function step1_getOAuthLink() {
log('Step 1: Checking VPS panel state...');
log('Step 1: Waiting for VPS panel to load (auto-login may take a moment)...');
// --- Selector strategy ---
// The VPS panel is at management.html#/oauth
// We need to: 1) find OAuth login section, 2) click Codex login, 3) read auth URL
// TODO: These selectors MUST be adjusted after inspecting the actual VPS panel DOM
// The selectors below are best-guess placeholders.
// Try to find OAuth login button by text content
log('Step 1: Looking for OAuth login button...');
let oauthBtn = null;
// The page may start at #/login and auto-redirect to #/oauth.
// Wait for the Codex OAuth card to appear (up to 30s for auto-login + redirect).
let loginBtn = null;
try {
oauthBtn = await waitForElementByText(
'button, a, [role="button"], .el-button, div[class*="btn"]',
/oauth|OAuth/i,
10000
);
// Wait for any card-header containing "Codex" to appear
const header = await waitForElementByText('.card-header', /codex/i, 30000);
loginBtn = header.querySelector('button.btn.btn-primary, button.btn');
log('Step 1: Found Codex OAuth card');
} catch {
// Fallback: try common UI framework selectors
try {
oauthBtn = await waitForElement('.oauth-login-btn, [data-action="oauth-login"]', 5000);
} catch {
throw new Error(
'Could not find OAuth login button. ' +
'Please inspect the VPS panel page in DevTools and update the selector in vps-panel.js. ' +
'URL: ' + location.href
);
}
}
simulateClick(oauthBtn);
log('Step 1: Clicked OAuth login, waiting for Codex login option...');
await sleep(1500);
// Wait for Codex login option to appear
let codexBtn = null;
try {
codexBtn = await waitForElementByText(
'button, a, [role="button"], .el-button, div[class*="btn"], span',
/codex/i,
10000
);
} catch {
try {
codexBtn = await waitForElement('[data-action="codex-login"], .codex-login-btn', 5000);
} catch {
throw new Error(
'Could not find Codex login button after clicking OAuth. ' +
'Check the VPS panel DOM in DevTools. URL: ' + location.href
);
}
}
simulateClick(codexBtn);
log('Step 1: Clicked Codex login, waiting for auth URL...');
await sleep(2000);
// Extract the auth URL — could be in various elements
let oauthUrl = null;
// Strategy 1: Look for an input/textarea with a URL value
const inputs = document.querySelectorAll('input[readonly], input[type="text"], textarea, code, pre');
for (const el of inputs) {
const val = (el.value || el.textContent || '').trim();
if (val.startsWith('http') && val.length > 30) {
oauthUrl = val;
log(`Step 1: Found URL in <${el.tagName}>: ${val.slice(0, 80)}...`);
break;
}
}
// Strategy 2: Look for any element containing a long URL
if (!oauthUrl) {
const allElements = document.querySelectorAll('span, p, div, a, code, pre');
for (const el of allElements) {
const text = (el.textContent || '').trim();
// Match a URL that looks like an OAuth authorization URL
const urlMatch = text.match(/(https?:\/\/[^\s<>"']{30,})/);
if (urlMatch) {
oauthUrl = urlMatch[1];
log(`Step 1: Found URL in text: ${oauthUrl.slice(0, 80)}...`);
break;
}
}
}
// Strategy 3: Check clipboard (if the page auto-copies)
if (!oauthUrl) {
try {
oauthUrl = await navigator.clipboard.readText();
if (oauthUrl && oauthUrl.startsWith('http') && oauthUrl.length > 30) {
log(`Step 1: Found URL in clipboard: ${oauthUrl.slice(0, 80)}...`);
} else {
oauthUrl = null;
}
} catch {
// Clipboard access may be denied
}
}
if (!oauthUrl) {
throw new Error(
'Could not find auth URL. The URL may be displayed in a format we cannot detect. ' +
'Please check the VPS panel page and copy the URL manually, or update the extraction logic in vps-panel.js. ' +
'URL: ' + location.href
'Codex OAuth card did not appear after 30s. Page may still be loading or not logged in. ' +
'Current URL: ' + location.href
);
}
if (!loginBtn) {
throw new Error('Found Codex OAuth card but no login button inside it. URL: ' + location.href);
}
// Check if button is disabled (already clicked / loading)
if (loginBtn.disabled) {
log('Step 1: Login button is disabled (already loading), waiting for auth URL...');
} else {
simulateClick(loginBtn);
log('Step 1: Clicked login button, waiting for auth URL...');
}
// Wait for the auth URL to appear in the specific div
let authUrlEl = null;
try {
authUrlEl = await waitForElement('[class*="authUrlValue"]', 15000);
} catch {
throw new Error(
'Auth URL did not appear after clicking login. ' +
'Check if VPS panel is logged in and Codex service is running. URL: ' + location.href
);
}
const oauthUrl = (authUrlEl.textContent || '').trim();
if (!oauthUrl || !oauthUrl.startsWith('http')) {
throw new Error(`Invalid OAuth URL found: "${oauthUrl.slice(0, 50)}". Expected URL starting with http.`);
}
log(`Step 1: OAuth URL obtained: ${oauthUrl.slice(0, 80)}...`, 'ok');
reportComplete(1, { oauthUrl: oauthUrl.trim() });
reportComplete(1, { oauthUrl });
}
// ============================================================
// Step 9: VPS Verify
// Step 9: VPS Verify — paste localhost URL and submit
// ============================================================
async function step9_vpsVerify() {
log('Step 9: Getting localhost URL from storage...');
// Get localhostUrl from storage (via Background)
const state = await chrome.runtime.sendMessage({ type: 'GET_STATE' });
const localhostUrl = state.localhostUrl;
if (!localhostUrl) {
throw new Error('No localhost URL found. Complete step 8 first.');
}
log(`Step 9: Looking for URL input field on VPS panel...`);
log('Step 9: Looking for callback URL input...');
// Try to find URL input field
// Find the callback URL input
// Actual DOM: <input class="input" placeholder="http://localhost:1455/auth/callback?code=...&state=...">
let urlInput = null;
try {
urlInput = await waitForElement(
'input[placeholder*="localhost"], input[placeholder*="callback"], input[placeholder*="URL"], input[placeholder*="url"], input[name*="callback"], input[name*="url"]',
10000
);
urlInput = await waitForElement('[class*="callbackSection"] input.input', 10000);
} catch {
// Fallback: find any text input that's visible and empty
const inputs = document.querySelectorAll('input[type="text"], input:not([type])');
for (const input of inputs) {
if (input.offsetParent !== null && !input.value) {
urlInput = input;
log('Step 9: Using fallback empty input field');
break;
}
}
if (!urlInput) {
throw new Error(
'Could not find URL input field on VPS panel. ' +
'Check DOM structure in DevTools. URL: ' + location.href
);
try {
urlInput = await waitForElement('input[placeholder*="localhost"]', 5000);
} catch {
throw new Error('Could not find callback URL input on VPS panel. URL: ' + location.href);
}
}
fillInput(urlInput, localhostUrl);
log(`Step 9: Filled URL input with: ${localhostUrl}`);
log(`Step 9: Filled callback URL: ${localhostUrl.slice(0, 80)}...`);
// Find and click verify button
let verifyBtn = null;
// Find and click "提交回调 URL" button
let submitBtn = null;
try {
verifyBtn = await waitForElementByText(
'button, [role="button"], .el-button, a',
/verif|确认|验证|submit|提交/i,
10000
submitBtn = await waitForElementByText(
'[class*="callbackActions"] button, [class*="callbackSection"] button',
/提交/,
5000
);
} catch {
throw new Error(
'Could not find verify/submit button. ' +
'Check VPS panel DOM in DevTools. URL: ' + location.href
);
try {
submitBtn = await waitForElementByText('button.btn', /提交回调/, 5000);
} catch {
throw new Error('Could not find "提交回调 URL" button. URL: ' + location.href);
}
}
simulateClick(submitBtn);
log('Step 9: Clicked "提交回调 URL", waiting for authentication result...');
// Wait for "认证成功!" status badge to appear
try {
await waitForElementByText('.status-badge, [class*="status"]', /认证成功/, 30000);
log('Step 9: Authentication successful!', 'ok');
} catch {
// Check if there's an error message instead
const statusEl = document.querySelector('.status-badge, [class*="status"]');
const statusText = statusEl ? statusEl.textContent : 'unknown';
if (/成功|success/i.test(statusText)) {
log('Step 9: Authentication successful!', 'ok');
} else {
log(`Step 9: Status after submit: "${statusText}". May still be processing.`, 'warn');
}
}
simulateClick(verifyBtn);
log('Step 9: Clicked verify button', 'ok');
reportComplete(9);
}