Files
FlowPilot/flows/grok/content/register-page.js
T
2026-05-23 04:29:37 +08:00

318 lines
12 KiB
JavaScript

console.log('[MultiPage:grok-register-page] Content script loaded on', location.href);
const GROK_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-grok-register-page-listener';
const GROK_SIGNUP_URL = 'https://accounts.x.ai/sign-up?redirect=grok-com';
const GROK_EMAIL_SIGNUP_TEXT_PATTERN = /使用邮箱注册|sign\s*up\s*with\s*email|continue\s*with\s*email|email/i;
const GROK_CONTINUE_TEXT_PATTERN = /continue|next|sign\s*up|submit|verify|继续|下一步|注册|提交|验证/i;
const GROK_PROFILE_TEXT_PATTERN = /given\s*name|family\s*name|first\s*name|last\s*name|password|名字|姓氏|密码/i;
const GROK_PROFILE_SUBMIT_PRE_CLICK_DELAY_MS = 2000;
function isVisibleGrokElement(element) {
if (!element || !(element instanceof Element)) return false;
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function getGrokElementText(element) {
if (!element) return '';
return String(
element.innerText
|| element.textContent
|| element.getAttribute?.('aria-label')
|| element.getAttribute?.('title')
|| ''
)
.replace(/\s+/g, ' ')
.trim();
}
function queryVisibleGrokElement(selector) {
return Array.from(document.querySelectorAll(selector)).find(isVisibleGrokElement) || null;
}
function findGrokClickableByText(pattern) {
const selectors = 'button, a, [role="button"], input[type="button"], input[type="submit"]';
return Array.from(document.querySelectorAll(selectors)).find((element) => {
if (!isVisibleGrokElement(element)) return false;
const text = element instanceof HTMLInputElement ? element.value : getGrokElementText(element);
return pattern.test(text);
}) || null;
}
function simulateGrokClick(element) {
throwIfStopped();
if (!element) {
throw new Error('无法点击空元素。');
}
const rect = element.getBoundingClientRect();
const clientX = Math.max(0, Math.floor(rect.left + Math.min(rect.width - 1, Math.max(1, rect.width / 2))));
const clientY = Math.max(0, Math.floor(rect.top + Math.min(rect.height - 1, Math.max(1, rect.height / 2))));
const eventOptions = {
bubbles: true,
cancelable: true,
view: window,
clientX,
clientY,
screenX: window.screenX + clientX,
screenY: window.screenY + clientY,
};
element.dispatchEvent(new MouseEvent('mouseover', eventOptions));
element.dispatchEvent(new MouseEvent('mousedown', eventOptions));
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
if (typeof element.click === 'function') {
element.click();
return;
}
element.dispatchEvent(new MouseEvent('click', eventOptions));
}
async function waitForGrok(predicate, options = {}) {
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 30000);
const intervalMs = Math.max(100, Number(options.intervalMs) || 250);
const deadline = Date.now() + timeoutMs;
let lastValue = null;
while (Date.now() <= deadline) {
throwIfStopped();
lastValue = predicate();
if (lastValue) return lastValue;
await sleep(intervalMs);
}
return lastValue;
}
function findGrokEmailInput() {
return queryVisibleGrokElement([
'input[type="email"]',
'input[name="email" i]',
'input[autocomplete="email"]',
'input[placeholder*="email" i]',
'input[inputmode="email"]',
].join(', '));
}
function findGrokOtpInputs() {
const inputs = Array.from(document.querySelectorAll([
'input[autocomplete="one-time-code"]',
'input[inputmode="numeric"]',
'input[name*="otp" i]',
'input[name*="code" i]',
'input[aria-label*="code" i]',
'input[placeholder*="code" i]',
].join(', '))).filter(isVisibleGrokElement);
if (inputs.length) return inputs;
const oneCharInputs = Array.from(document.querySelectorAll('input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"])'))
.filter((input) => isVisibleGrokElement(input) && Number(input.maxLength || 0) === 1);
return oneCharInputs.length >= 4 ? oneCharInputs : [];
}
function findGrokProfileInput(names) {
const selectors = names.flatMap((name) => [
`input[name="${name}" i]`,
`input[id="${name}" i]`,
`input[autocomplete="${name}" i]`,
`input[placeholder*="${name}" i]`,
`input[aria-label*="${name}" i]`,
]).join(', ');
return queryVisibleGrokElement(selectors);
}
function findGrokPasswordInputs() {
return Array.from(document.querySelectorAll([
'input[type="password"]',
'input[name*="password" i]',
'input[autocomplete="new-password"]',
'input[placeholder*="password" i]',
'input[aria-label*="password" i]',
].join(', '))).filter(isVisibleGrokElement);
}
function findGrokSubmitButton(contextPattern = GROK_CONTINUE_TEXT_PATTERN) {
return findGrokClickableByText(contextPattern)
|| Array.from(document.querySelectorAll('button:not([disabled]), [role="button"]')).filter(isVisibleGrokElement).at(-1)
|| null;
}
function getGrokPageState() {
const pageText = document.body?.innerText || '';
if (/grok|xai|x\.ai/i.test(location.hostname) && /(?:^|;\s*)sso=/.test(document.cookie || '')) return 'signed_in';
if (findGrokProfileInput(['givenName', 'firstName']) || findGrokPasswordInputs().length || GROK_PROFILE_TEXT_PATTERN.test(pageText)) return 'profile_entry';
if (findGrokOtpInputs().length) return 'verification_code_entry';
if (findGrokEmailInput()) return 'email_entry';
return 'unknown';
}
async function openGrokSignupPage() {
if (!/accounts\.x\.ai$/i.test(location.hostname) || !/\/sign-up/i.test(location.pathname)) {
location.href = GROK_SIGNUP_URL;
return { submitted: true, state: 'navigating', url: location.href };
}
const emailButton = await waitForGrok(() => (
findGrokClickableByText(GROK_EMAIL_SIGNUP_TEXT_PATTERN) || findGrokEmailInput()
), { timeoutMs: 30000 });
if (!emailButton) throw new Error('未找到 x.ai 邮箱注册入口。');
if (!(emailButton instanceof HTMLInputElement)) {
simulateGrokClick(emailButton);
await sleep(500);
}
return { submitted: true, state: getGrokPageState(), url: location.href };
}
function getGrokEmailErrorText() {
const text = String(document.body?.innerText || '').trim();
const patterns = [
/Your email domain[^\n]+has been rejected[^\n]*/i,
/Please use a different email address[^\n]*/i,
/邮箱域名[^\n]*(?:被拒绝|不可用|不支持)[^\n]*/i,
/请使用其他邮箱[^\n]*/i,
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match?.[0]) return match[0].trim();
}
return '';
}
async function submitGrokEmail(payload = {}) {
const email = String(payload.email || '').trim();
if (!email) throw new Error('缺少 Grok 注册邮箱。');
const input = await waitForGrok(findGrokEmailInput, { timeoutMs: 45000 });
if (!input) throw new Error('未找到 x.ai 邮箱输入框。');
fillInput(input, email);
await sleep(200);
const button = findGrokSubmitButton();
if (!button) throw new Error('未找到 x.ai 邮箱提交按钮。');
simulateGrokClick(button);
await sleep(1200);
const errorText = getGrokEmailErrorText();
if (errorText) {
throw new Error(errorText);
}
return { submitted: true, state: getGrokPageState(), url: location.href };
}
function getGrokVerificationErrorText() {
const text = String(document.body?.innerText || '').trim();
const patterns = [
/(?:verification|confirmation)?\s*code\s*(?:is\s*)?(?:invalid|incorrect|expired)[^\n]*/i,
/invalid\s*(?:verification|confirmation)?\s*code[^\n]*/i,
/验证码[^\n]*(?:错误|无效|过期)[^\n]*/i,
/代码[^\n]*(?:错误|无效|过期)[^\n]*/i,
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match?.[0]) return match[0].trim();
}
return '';
}
async function submitGrokVerificationCode(payload = {}) {
const normalizedCode = String(payload.code || '').replace(/[^A-Za-z0-9]/g, '').trim();
if (!normalizedCode) throw new Error('缺少 xAI 验证码。');
const inputs = await waitForGrok(() => findGrokOtpInputs(), { timeoutMs: 45000 });
if (!inputs?.length) throw new Error('未找到 xAI 验证码输入框。');
if (inputs.length === 1) {
fillInput(inputs[0], normalizedCode);
} else {
normalizedCode.split('').forEach((char, index) => {
if (inputs[index]) fillInput(inputs[index], char);
});
}
await sleep(200);
const button = findGrokSubmitButton();
if (button) simulateGrokClick(button);
const settledState = await waitForGrok(() => {
const errorText = getGrokVerificationErrorText();
if (errorText) return { state: 'verification_error', error: errorText };
const state = getGrokPageState();
return state && state !== 'verification_code_entry' ? { state } : null;
}, { timeoutMs: 20000, intervalMs: 500 });
const finalState = settledState?.state || getGrokPageState();
if (settledState?.error) {
throw new Error(settledState.error);
}
if (finalState === 'email_entry') {
throw new Error('x.ai 验证码提交后回到邮箱注册页,可能是验证码无效、会话过期或注册风控重置。');
}
if (!['profile_entry', 'signed_in'].includes(finalState)) {
throw new Error(`x.ai 验证码提交后进入未知页面状态:${finalState || 'unknown'}`);
}
return { submitted: true, state: finalState, url: location.href };
}
async function submitGrokProfile(payload = {}) {
const firstName = String(payload.firstName || '').trim();
const lastName = String(payload.lastName || '').trim();
const password = String(payload.password || '');
if (!firstName || !lastName || !password) throw new Error('缺少 Grok 注册资料。');
const ready = await waitForGrok(() => {
const firstInput = findGrokProfileInput(['givenName', 'firstName', 'given-name']);
const lastInput = findGrokProfileInput(['familyName', 'lastName', 'family-name']);
const passwordInputs = findGrokPasswordInputs();
return firstInput && lastInput && passwordInputs.length ? { firstInput, lastInput, passwordInputs } : null;
}, { timeoutMs: 45000 });
if (!ready) throw new Error('未找到 x.ai 资料或密码表单。');
fillInput(ready.firstInput, firstName);
fillInput(ready.lastInput, lastName);
ready.passwordInputs.forEach((input) => fillInput(input, password));
await sleep(GROK_PROFILE_SUBMIT_PRE_CLICK_DELAY_MS);
const button = findGrokSubmitButton();
if (!button) throw new Error('未找到 x.ai 资料提交按钮。');
simulateGrokClick(button);
return { submitted: true, state: 'profile_submitted', url: location.href };
}
async function extractGrokSsoCookie() {
const match = String(document.cookie || '').match(/(?:^|;\s*)sso=([^;]+)/);
return {
submitted: true,
state: match ? 'sso_cookie_found' : getGrokPageState(),
ssoCookie: match ? decodeURIComponent(match[1]) : '',
url: location.href,
};
}
async function executeGrokCommand(command, payload = {}) {
switch (command) {
case 'grok-open-signup-page':
return openGrokSignupPage(payload);
case 'grok-submit-email':
return submitGrokEmail(payload);
case 'grok-submit-verification-code':
return submitGrokVerificationCode(payload);
case 'grok-submit-profile':
return submitGrokProfile(payload);
case 'grok-extract-sso-cookie':
return extractGrokSsoCookie(payload);
case 'GET_PAGE_STATE':
return { state: getGrokPageState(), url: location.href };
default:
throw new Error(`未知 Grok 注册命令:${command}`);
}
}
if (!document.documentElement.hasAttribute(GROK_REGISTER_PAGE_LISTENER_SENTINEL)) {
document.documentElement.setAttribute(GROK_REGISTER_PAGE_LISTENER_SENTINEL, '1');
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.type !== 'EXECUTE_NODE' && message?.type !== 'GET_PAGE_STATE') return false;
resetStopState();
const command = message.command || message.nodeId || message.type;
executeGrokCommand(command, message.payload || {})
.then((result) => sendResponse({ ok: true, ...result }))
.catch((error) => {
if (isStopError(error)) {
sendResponse({ stopped: true, error: error.message });
return;
}
sendResponse({ ok: false, error: error?.message || String(error) });
});
return true;
});
}
window.__MULTIPAGE_GROK_REGISTER_PAGE__ = {
executeGrokCommand,
getGrokPageState,
};