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

692 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function attachBackgroundGrokRegisterRunner(root, factory) {
root.MultiPageBackgroundGrokRegisterRunner = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGrokRegisterRunnerModule() {
const GROK_SIGNUP_URL = 'https://accounts.x.ai/sign-up?redirect=grok-com';
const GROK_REGISTER_PAGE_SOURCE_ID = 'grok-register-page';
const DEFAULT_GROK_PAGE_TIMEOUT_MS = 90 * 1000;
const GROK_POST_PROFILE_CF_WAIT_MS = 20 * 1000;
const GROK_PRE_SSO_EXTRACT_WAIT_MS = 10 * 1000;
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
const GROK_COOKIE_CLEAR_DOMAINS = Object.freeze([
'x.ai',
'accounts.x.ai',
'grok.com',
]);
function cleanString(value = '') {
return String(value ?? '').trim();
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : cleanString(error) || '未知错误';
}
function createGeneratedPassword() {
const alphabet = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*';
let output = '';
for (let index = 0; index < 18; index += 1) {
output += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return `${output}aA1!`;
}
function createGrokRegisterRunner(deps = {}) {
const {
addLog = async () => {},
chrome = (typeof globalThis !== 'undefined' ? globalThis.chrome : null),
completeNodeFromBackground,
ensureContentScriptReadyOnTab = null,
generatePassword = null,
generateRandomName = null,
getState = async () => ({}),
getTabId = async () => null,
isTabAlive = async () => false,
pollFlowVerificationCode = null,
registerTab = async () => {},
resolveSignupEmailForFlow = null,
reuseOrCreateTab = async () => null,
sendToContentScriptResilient = null,
setPasswordState = async () => {},
setState = async () => {},
sleepWithStop = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
},
throwIfStopped = () => {},
waitForTabStableComplete = null,
GROK_REGISTER_INJECT_FILES = null,
markCurrentRegistrationAccountUsed = null,
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Grok register runner requires completeNodeFromBackground.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function activateTab(tabId) {
if (!Number.isInteger(tabId) || !chrome?.tabs?.update) {
return;
}
await chrome.tabs.update(tabId, { active: true });
}
async function getExecutionState(state = {}) {
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
return state;
}
return getState();
}
async function persistState(patch = {}) {
await setState(patch);
return patch;
}
function buildGrokRuntimePatch(patch = {}) {
return {
runtimeState: {
flowState: {
grok: patch,
},
},
};
}
async function completeNode(nodeId, patch = {}) {
await persistState(patch);
await completeNodeFromBackground(nodeId, patch);
return patch;
}
async function isUsableTabId(tabId) {
if (!Number.isInteger(tabId)) {
return false;
}
if (typeof isTabAlive === 'function' && await isTabAlive(GROK_REGISTER_PAGE_SOURCE_ID)) {
return true;
}
if (chrome?.tabs?.get) {
const tab = await chrome.tabs.get(tabId).catch(() => null);
return Boolean(tab?.id === tabId);
}
return true;
}
async function ensureGrokRegisterTab(state = {}, options = {}) {
const existingTabId = Number(
state?.grokRegisterTabId
|| state?.runtimeState?.flowState?.grok?.session?.registerTabId
|| state?.tabRegistry?.[GROK_REGISTER_PAGE_SOURCE_ID]?.tabId
|| 0
);
if (Number.isInteger(existingTabId) && existingTabId > 0 && await isUsableTabId(existingTabId)) {
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, existingTabId);
return existingTabId;
}
const tabId = await getTabId(GROK_REGISTER_PAGE_SOURCE_ID);
if (Number.isInteger(tabId) && await isUsableTabId(tabId)) {
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, tabId);
return tabId;
}
if (!options.openIfMissing) {
throw new Error(options.missingMessage || '缺少 Grok 注册页,请先执行步骤 1。');
}
const openedTabId = await reuseOrCreateTab(GROK_REGISTER_PAGE_SOURCE_ID, GROK_SIGNUP_URL, {
inject: Array.isArray(GROK_REGISTER_INJECT_FILES) ? GROK_REGISTER_INJECT_FILES : null,
injectSource: GROK_REGISTER_PAGE_SOURCE_ID,
});
if (!Number.isInteger(openedTabId)) {
throw new Error('无法打开 Grok 注册页。');
}
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, openedTabId);
return openedTabId;
}
async function ensureContentReady(tabId, options = {}) {
if (!Number.isInteger(tabId)) {
throw new Error('缺少 Grok 注册页标签页,无法连接内容脚本。');
}
if (typeof waitForTabStableComplete === 'function') {
await waitForTabStableComplete(tabId, {
timeoutMs: options.timeoutMs || DEFAULT_GROK_PAGE_TIMEOUT_MS,
retryDelayMs: 300,
stableMs: Number(options.stableMs) || 1200,
initialDelayMs: Number(options.initialDelayMs) || 120,
});
}
if (typeof ensureContentScriptReadyOnTab === 'function') {
await ensureContentScriptReadyOnTab(GROK_REGISTER_PAGE_SOURCE_ID, tabId, {
inject: Array.isArray(GROK_REGISTER_INJECT_FILES) ? GROK_REGISTER_INJECT_FILES : null,
injectSource: GROK_REGISTER_PAGE_SOURCE_ID,
timeoutMs: options.timeoutMs || DEFAULT_GROK_PAGE_TIMEOUT_MS,
retryDelayMs: 700,
logMessage: options.logMessage || 'Grok 注册页内容脚本未就绪,正在等待页面恢复...',
});
}
}
async function sendGrokCommand(nodeId, payload = {}, options = {}) {
if (typeof sendToContentScriptResilient !== 'function') {
throw new Error('Grok 注册页通信能力不可用。');
}
const result = await sendToContentScriptResilient(GROK_REGISTER_PAGE_SOURCE_ID, {
type: 'EXECUTE_NODE',
nodeId,
step: options.step || 0,
source: 'background',
payload,
}, {
timeoutMs: options.timeoutMs || 45000,
retryDelayMs: 700,
logMessage: options.logMessage || '',
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
function shouldClearGrokCookie(cookie = {}) {
const domain = cleanString(cookie.domain).replace(/^\.+/, '').toLowerCase();
return GROK_COOKIE_CLEAR_DOMAINS.some((target) => (
domain === target || domain.endsWith(`.${target}`)
));
}
function buildCookieRemovalUrl(cookie = {}) {
const host = cleanString(cookie.domain).replace(/^\.+/, '').toLowerCase();
const path = cleanString(cookie.path) || '/';
return `https://${host}${path.startsWith('/') ? path : `/${path}`}`;
}
async function clearGrokCookiesBeforeStep1() {
if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) {
await log('步骤 1:当前浏览器不支持 cookies API,跳过 Grok Cookie 清理。', 'warn', 'grok-open-signup-page');
return;
}
const stores = chrome.cookies.getAllCookieStores
? await chrome.cookies.getAllCookieStores()
: [{ id: undefined }];
let removedCount = 0;
const seen = new Set();
for (const store of stores) {
const storeId = store?.id;
const cookies = await chrome.cookies.getAll(storeId ? { storeId } : {}).catch(() => []);
for (const cookie of cookies || []) {
if (!shouldClearGrokCookie(cookie)) {
continue;
}
const key = [
cookie.storeId || storeId || '',
cookie.domain || '',
cookie.path || '',
cookie.name || '',
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
].join('|');
if (seen.has(key)) {
continue;
}
seen.add(key);
try {
const details = {
url: buildCookieRemovalUrl(cookie),
name: cookie.name,
};
if (cookie.storeId) {
details.storeId = cookie.storeId;
}
if (cookie.partitionKey) {
details.partitionKey = cookie.partitionKey;
}
const removed = await chrome.cookies.remove(details);
if (removed) {
removedCount += 1;
}
} catch (error) {
console.warn('[MultiPage:grok-register] remove cookie failed', {
domain: cookie?.domain,
name: cookie?.name,
message: getErrorMessage(error),
});
}
}
}
await log(`步骤 1:已清理 Grok/xAI Cookie ${removedCount} 个。`, removedCount ? 'ok' : 'info', 'grok-open-signup-page');
}
function resolveProfile(currentState = {}) {
const firstFromState = cleanString(currentState.grokFirstName);
const lastFromState = cleanString(currentState.grokLastName);
if (firstFromState && lastFromState) {
return {
firstName: firstFromState,
lastName: lastFromState,
};
}
const generated = typeof generateRandomName === 'function' ? generateRandomName() : null;
const fullName = cleanString(generated?.fullName || generated?.name || 'Alex Morgan');
const parts = fullName.split(/\s+/).filter(Boolean);
return {
firstName: firstFromState || cleanString(generated?.firstName || parts[0] || 'Alex'),
lastName: lastFromState || cleanString(generated?.lastName || parts.slice(1).join(' ') || 'Morgan'),
};
}
function resolvePassword(currentState = {}) {
return cleanString(currentState.grokPassword || currentState.customPassword || currentState.password)
|| (typeof generatePassword === 'function' ? generatePassword() : createGeneratedPassword());
}
function normalizeGrokVerificationCode(value = '') {
return cleanString(value).replace(/[^A-Za-z0-9]/g, '');
}
async function readSsoCookieFromChrome() {
if (!chrome?.cookies?.get) {
return '';
}
const candidates = [
{ url: 'https://x.ai/', name: 'sso' },
{ url: 'https://grok.com/', name: 'sso' },
{ url: 'https://accounts.x.ai/', name: 'sso' },
];
for (const details of candidates) {
const cookie = await chrome.cookies.get(details).catch(() => null);
const value = cleanString(cookie?.value);
if (value) {
return value;
}
}
return '';
}
async function executeGrokOpenSignupPage(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-open-signup-page';
const currentState = await getExecutionState(state);
try {
await clearGrokCookiesBeforeStep1();
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: true });
await activateTab(tabId);
await persistState({
grokRegisterTabId: tabId,
grokSignupUrl: GROK_SIGNUP_URL,
...buildGrokRuntimePatch({
session: {
registerTabId: tabId,
startedAt: Date.now(),
pageUrl: GROK_SIGNUP_URL,
lastError: '',
},
}),
});
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {}, {
step: 1,
timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS,
logMessage: '步骤 1:正在打开 Grok 邮箱注册入口...',
});
await log('步骤 1:已打开 Grok 邮箱注册页。', 'ok', nodeId);
await completeNode(nodeId, {
grokRegisterTabId: tabId,
grokPageState: result.state || 'email_signup_ready',
grokPageUrl: result.url || GROK_SIGNUP_URL,
...buildGrokRuntimePatch({
session: {
registerTabId: tabId,
startedAt: Date.now(),
pageState: result.state || 'email_signup_ready',
pageUrl: result.url || GROK_SIGNUP_URL,
lastError: '',
},
register: {
status: 'signup_page_opened',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
}));
await log(`步骤 1${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitEmail(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-email';
const currentState = await getExecutionState(state);
try {
if (typeof resolveSignupEmailForFlow !== 'function') {
throw new Error('Grok 邮箱步骤缺少公共邮箱解析能力,无法继续执行。');
}
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await ensureContentReady(tabId);
const resolvedEmail = await resolveSignupEmailForFlow(currentState, {
preserveAccountIdentity: true,
});
const email = cleanString(resolvedEmail).toLowerCase();
if (!email) {
throw new Error('Grok 注册邮箱为空,无法继续执行。');
}
const requestedAt = Date.now();
await persistState({
grokEmail: email,
email,
accountIdentifierType: 'email',
accountIdentifier: email,
...buildGrokRuntimePatch({
register: {
email,
verificationRequestedAt: requestedAt,
status: 'email_submitting',
},
}),
});
const result = await sendGrokCommand(nodeId, { email }, {
step: 2,
logMessage: '步骤 2:正在提交 Grok 注册邮箱...',
});
await log(`步骤 2:已提交 Grok 注册邮箱 ${email}`, 'ok', nodeId);
await completeNode(nodeId, {
grokEmail: email,
grokVerificationRequestedAt: requestedAt,
grokPageState: result.state || '',
grokPageUrl: result.url || '',
email,
accountIdentifierType: 'email',
accountIdentifier: email,
...buildGrokRuntimePatch({
session: {
pageState: result.state || '',
pageUrl: result.url || '',
lastError: '',
},
register: {
email,
verificationRequestedAt: requestedAt,
status: 'verification_requested',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 2${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitVerificationCode(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-verification-code';
const currentState = await getExecutionState(state);
try {
if (typeof pollFlowVerificationCode !== 'function') {
throw new Error('Grok 验证码步骤缺少共享邮件轮询能力,无法继续执行。');
}
const requestedAt = Math.max(
0,
Number(
currentState.grokVerificationRequestedAt
|| currentState.runtimeState?.flowState?.grok?.register?.verificationRequestedAt
) || Date.now()
);
const filterAfterTimestamp = cleanString(currentState?.mailProvider).toLowerCase() === '2925'
? Math.max(0, requestedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: requestedAt;
const email = cleanString(
currentState.grokEmail
|| currentState.runtimeState?.flowState?.grok?.register?.email
|| currentState.email
).toLowerCase();
const pollResult = await pollFlowVerificationCode({
actionLabel: 'Grok 验证码',
filterAfterTimestamp,
flowId: 'grok',
logStep: 3,
logStepKey: nodeId,
nodeId,
notFoundMessage: '步骤 3:邮箱轮询结束,但未获取到 xAI 验证码。',
state: {
...currentState,
activeFlowId: 'grok',
flowId: 'grok',
visibleStep: 3,
grokEmail: email,
email,
},
step: 3,
});
const code = normalizeGrokVerificationCode(pollResult?.code);
if (!code) {
throw new Error('未能获取到 xAI 邮箱验证码。');
}
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, { code }, {
step: 3,
logMessage: '步骤 3:正在填写 xAI 邮箱验证码...',
});
await log(`步骤 3:已提交 xAI 邮箱验证码,当前页面状态:${result.state || 'unknown'}`, 'ok', nodeId);
await completeNode(nodeId, {
grokVerificationCode: code,
grokVerificationRawCode: cleanString(pollResult?.code),
grokVerificationMessageId: cleanString(pollResult?.messageId || pollResult?.mailId),
grokPageState: result.state || '',
grokPageUrl: result.url || '',
...buildGrokRuntimePatch({
session: {
pageState: result.state || '',
pageUrl: result.url || '',
lastError: '',
},
register: {
verificationCode: code,
status: 'verified',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 3${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitProfile(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-profile';
const currentState = await getExecutionState(state);
try {
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
const profile = resolveProfile(currentState);
const password = resolvePassword(currentState);
await persistState({
grokFirstName: profile.firstName,
grokLastName: profile.lastName,
grokPassword: password,
...buildGrokRuntimePatch({
register: {
firstName: profile.firstName,
lastName: profile.lastName,
password,
status: 'profile_submitting',
},
}),
});
if (typeof setPasswordState === 'function') {
await setPasswordState(password);
}
await activateTab(tabId);
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {
firstName: profile.firstName,
lastName: profile.lastName,
password,
}, {
step: 4,
logMessage: '步骤 4:正在填写 xAI 注册资料...',
});
await log(`步骤 4:已提交 Grok 注册资料,等待 ${Math.floor(GROK_POST_PROFILE_CF_WAIT_MS / 1000)} 秒完成注册验证...`, 'info', nodeId);
await sleepWithStop(GROK_POST_PROFILE_CF_WAIT_MS);
await ensureContentReady(tabId, { timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS });
await log('步骤 4:已提交 Grok 注册资料并完成等待。', 'ok', nodeId);
await completeNode(nodeId, {
grokFirstName: profile.firstName,
grokLastName: profile.lastName,
grokPassword: password,
grokPageState: result.state || 'profile_submitted',
grokPageUrl: result.url || '',
...buildGrokRuntimePatch({
session: {
pageState: result.state || 'profile_submitted',
pageUrl: result.url || '',
lastError: '',
},
register: {
firstName: profile.firstName,
lastName: profile.lastName,
password,
status: 'profile_submitted',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 4${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokExtractSsoCookie(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-extract-sso-cookie';
const currentState = await getExecutionState(state);
try {
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await log(`步骤 5:等待 ${Math.floor(GROK_PRE_SSO_EXTRACT_WAIT_MS / 1000)} 秒后提取 Grok SSO...`, 'info', nodeId);
await sleepWithStop(GROK_PRE_SSO_EXTRACT_WAIT_MS);
let ssoCookie = await readSsoCookieFromChrome();
if (!ssoCookie) {
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {}, {
step: 5,
logMessage: '步骤 5:正在从 Grok 注册页读取 sso Cookie...',
});
ssoCookie = cleanString(result?.ssoCookie);
}
if (!ssoCookie) {
throw new Error('未找到 x.ai/grok sso Cookie。');
}
const latestState = await getState();
const existingSsoCookies = Array.isArray(latestState?.grokSsoCookies)
? latestState.grokSsoCookies
.map((entry) => cleanString(entry))
.filter(Boolean)
: [];
const nextSsoCookies = existingSsoCookies.includes(ssoCookie)
? existingSsoCookies
: [...existingSsoCookies, ssoCookie];
const completedAt = Date.now();
const completionPatch = {
grokSsoCookie: ssoCookie,
grokSsoCookies: nextSsoCookies,
grokSsoExtractedAt: completedAt,
grokCompletedAt: completedAt,
grokRegisterStatus: 'completed',
...buildGrokRuntimePatch({
register: {
status: 'completed',
completedAt,
},
sso: {
currentCookie: ssoCookie,
cookies: nextSsoCookies,
extractedAt: completedAt,
},
session: {
lastError: '',
},
}),
};
if (typeof markCurrentRegistrationAccountUsed === 'function') {
await markCurrentRegistrationAccountUsed({
...currentState,
...completionPatch,
}, {
logPrefix: 'Grok 注册成功',
level: 'ok',
});
}
await log('步骤 5:已提取 Grok SSO Cookie。', 'ok', nodeId);
await completeNode(nodeId, completionPatch);
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 5${message}`, 'error', nodeId);
throw error;
}
}
return {
executeGrokExtractSsoCookie,
executeGrokOpenSignupPage,
executeGrokSubmitEmail,
executeGrokSubmitProfile,
executeGrokSubmitVerificationCode,
};
}
return {
DEFAULT_GROK_PAGE_TIMEOUT_MS,
GROK_COOKIE_CLEAR_DOMAINS,
GROK_POST_PROFILE_CF_WAIT_MS,
GROK_PRE_SSO_EXTRACT_WAIT_MS,
GROK_REGISTER_PAGE_SOURCE_ID,
GROK_SIGNUP_URL,
createGrokRegisterRunner,
};
});