signup-page.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // content/signup-page.js — Content script for OpenAI auth pages (steps 2, 3, 4-receive, 5)
  2. // Injected on: auth0.openai.com, auth.openai.com, accounts.openai.com
  3. console.log('[MultiPage:signup-page] Content script loaded on', location.href);
  4. // Listen for commands from Background
  5. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  6. if (message.type === 'EXECUTE_STEP' || message.type === 'FILL_CODE') {
  7. resetStopState();
  8. handleCommand(message).then(() => {
  9. sendResponse({ ok: true });
  10. }).catch(err => {
  11. if (isStopError(err)) {
  12. log(`Step ${message.step}: Stopped by user.`, 'warn');
  13. sendResponse({ stopped: true, error: err.message });
  14. return;
  15. }
  16. reportError(message.step, err.message);
  17. sendResponse({ error: err.message });
  18. });
  19. return true;
  20. }
  21. });
  22. async function handleCommand(message) {
  23. switch (message.type) {
  24. case 'EXECUTE_STEP':
  25. switch (message.step) {
  26. case 2: return await step2_clickRegister();
  27. case 3: return await step3_fillEmailPassword(message.payload);
  28. case 5: return await step5_fillNameBirthday(message.payload);
  29. case 6: return await step6_login(message.payload);
  30. case 8: return await step8_clickContinue();
  31. default: throw new Error(`signup-page.js does not handle step ${message.step}`);
  32. }
  33. case 'FILL_CODE':
  34. // Step 4 = signup code, Step 7 = login code (same handler)
  35. return await fillVerificationCode(message.step, message.payload);
  36. }
  37. }
  38. // ============================================================
  39. // Step 2: Click Register
  40. // ============================================================
  41. async function step2_clickRegister() {
  42. log('Step 2: Looking for Register/Sign up button...');
  43. let registerBtn = null;
  44. try {
  45. registerBtn = await waitForElementByText(
  46. 'a, button, [role="button"], [role="link"]',
  47. /sign\s*up|register|create\s*account|注册/i,
  48. 10000
  49. );
  50. } catch {
  51. // Some pages may have a direct link
  52. try {
  53. registerBtn = await waitForElement('a[href*="signup"], a[href*="register"]', 5000);
  54. } catch {
  55. throw new Error(
  56. 'Could not find Register/Sign up button. ' +
  57. 'Check auth page DOM in DevTools. URL: ' + location.href
  58. );
  59. }
  60. }
  61. await humanPause(450, 1200);
  62. reportComplete(2);
  63. simulateClick(registerBtn);
  64. log('Step 2: Clicked Register button');
  65. }
  66. // ============================================================
  67. // Step 3: Fill Email & Password
  68. // ============================================================
  69. async function step3_fillEmailPassword(payload) {
  70. const { email } = payload;
  71. if (!email) throw new Error('No email provided. Paste email in Side Panel first.');
  72. log(`Step 3: Filling email: ${email}`);
  73. // Find email input
  74. let emailInput = null;
  75. try {
  76. emailInput = await waitForElement(
  77. 'input[type="email"], input[name="email"], input[name="username"], input[id*="email"], input[placeholder*="email"], input[placeholder*="Email"]',
  78. 10000
  79. );
  80. } catch {
  81. throw new Error('Could not find email input field on signup page. URL: ' + location.href);
  82. }
  83. await humanPause(500, 1400);
  84. fillInput(emailInput, email);
  85. log('Step 3: Email filled');
  86. // Check if password field is on the same page
  87. let passwordInput = document.querySelector('input[type="password"]');
  88. if (!passwordInput) {
  89. // Need to submit email first to get to password page
  90. log('Step 3: No password field yet, submitting email first...');
  91. const submitBtn = document.querySelector('button[type="submit"]')
  92. || await waitForElementByText('button', /continue|next|submit|继续|下一步/i, 5000).catch(() => null);
  93. if (submitBtn) {
  94. await humanPause(400, 1100);
  95. simulateClick(submitBtn);
  96. log('Step 3: Submitted email, waiting for password field...');
  97. await sleep(2000);
  98. }
  99. try {
  100. passwordInput = await waitForElement('input[type="password"]', 10000);
  101. } catch {
  102. throw new Error('Could not find password input after submitting email. URL: ' + location.href);
  103. }
  104. }
  105. if (!payload.password) throw new Error('No password provided. Step 3 requires a generated password.');
  106. await humanPause(600, 1500);
  107. fillInput(passwordInput, payload.password);
  108. log('Step 3: Password filled');
  109. // Report complete BEFORE submit, because submit causes page navigation
  110. // which kills the content script connection
  111. reportComplete(3, { email });
  112. // Submit the form (page will navigate away after this)
  113. await sleep(500);
  114. const submitBtn = document.querySelector('button[type="submit"]')
  115. || await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|create/i, 5000).catch(() => null);
  116. if (submitBtn) {
  117. await humanPause(500, 1300);
  118. simulateClick(submitBtn);
  119. log('Step 3: Form submitted');
  120. }
  121. }
  122. // ============================================================
  123. // Fill Verification Code (used by step 4 and step 7)
  124. // ============================================================
  125. async function fillVerificationCode(step, payload) {
  126. const { code } = payload;
  127. if (!code) throw new Error('No verification code provided.');
  128. log(`Step ${step}: Filling verification code: ${code}`);
  129. // Find code input — could be a single input or multiple separate inputs
  130. let codeInput = null;
  131. try {
  132. codeInput = await waitForElement(
  133. 'input[name="code"], input[name="otp"], input[type="text"][maxlength="6"], input[aria-label*="code"], input[placeholder*="code"], input[placeholder*="Code"], input[inputmode="numeric"]',
  134. 10000
  135. );
  136. } catch {
  137. // Check for multiple single-digit inputs (common pattern)
  138. const singleInputs = document.querySelectorAll('input[maxlength="1"]');
  139. if (singleInputs.length >= 6) {
  140. log(`Step ${step}: Found single-digit code inputs, filling individually...`);
  141. for (let i = 0; i < 6 && i < singleInputs.length; i++) {
  142. fillInput(singleInputs[i], code[i]);
  143. await sleep(100);
  144. }
  145. await sleep(1000);
  146. reportComplete(step);
  147. return;
  148. }
  149. throw new Error('Could not find verification code input. URL: ' + location.href);
  150. }
  151. fillInput(codeInput, code);
  152. log(`Step ${step}: Code filled`);
  153. // Report complete BEFORE submit (page may navigate away)
  154. reportComplete(step);
  155. // Submit
  156. await sleep(500);
  157. const submitBtn = document.querySelector('button[type="submit"]')
  158. || await waitForElementByText('button', /verify|confirm|submit|continue|确认|验证/i, 5000).catch(() => null);
  159. if (submitBtn) {
  160. await humanPause(450, 1200);
  161. simulateClick(submitBtn);
  162. log(`Step ${step}: Verification submitted`);
  163. }
  164. }
  165. // ============================================================
  166. // Step 6: Login with registered account (on OAuth auth page)
  167. // ============================================================
  168. async function step6_login(payload) {
  169. const { email, password } = payload;
  170. if (!email) throw new Error('No email provided for login.');
  171. log(`Step 6: Logging in with ${email}...`);
  172. // Wait for email input on the auth page
  173. let emailInput = null;
  174. try {
  175. emailInput = await waitForElement(
  176. 'input[type="email"], input[name="email"], input[name="username"], input[id*="email"], input[placeholder*="email" i], input[placeholder*="Email"]',
  177. 15000
  178. );
  179. } catch {
  180. throw new Error('Could not find email input on login page. URL: ' + location.href);
  181. }
  182. await humanPause(500, 1400);
  183. fillInput(emailInput, email);
  184. log('Step 6: Email filled');
  185. // Submit email
  186. await sleep(500);
  187. const submitBtn1 = document.querySelector('button[type="submit"]')
  188. || await waitForElementByText('button', /continue|next|submit|继续|下一步/i, 5000).catch(() => null);
  189. if (submitBtn1) {
  190. await humanPause(400, 1100);
  191. simulateClick(submitBtn1);
  192. log('Step 6: Submitted email');
  193. }
  194. await sleep(2000);
  195. // Check for password field
  196. const passwordInput = document.querySelector('input[type="password"]');
  197. if (passwordInput) {
  198. log('Step 6: Password field found, filling password...');
  199. await humanPause(550, 1450);
  200. fillInput(passwordInput, password);
  201. await sleep(500);
  202. const submitBtn2 = document.querySelector('button[type="submit"]')
  203. || await waitForElementByText('button', /continue|log\s*in|submit|sign\s*in|登录|继续/i, 5000).catch(() => null);
  204. // Report complete BEFORE submit in case page navigates
  205. reportComplete(6, { needsOTP: true });
  206. if (submitBtn2) {
  207. await humanPause(450, 1200);
  208. simulateClick(submitBtn2);
  209. log('Step 6: Submitted password, may need verification code (step 7)');
  210. }
  211. return;
  212. }
  213. // No password field — OTP flow
  214. log('Step 6: No password field. OTP flow or auto-redirect.');
  215. reportComplete(6, { needsOTP: true });
  216. }
  217. // ============================================================
  218. // Step 8: Focus "继续" on OAuth consent page for manual click
  219. // ============================================================
  220. // After login + verification, page shows:
  221. // "使用 ChatGPT 登录到 Codex" with a "继续" submit button.
  222. // We only locate and focus it so the user can click manually.
  223. async function step8_clickContinue() {
  224. log('Step 8: Looking for OAuth consent "继续" button for manual click...');
  225. // Wait for the consent page to be ready
  226. // Look for the submit button with text "继续" or data-dd-action-name="Continue"
  227. let continueBtn = null;
  228. try {
  229. continueBtn = await waitForElement(
  230. 'button[type="submit"][data-dd-action-name="Continue"], button[type="submit"]._primary_3rdp0_107',
  231. 10000
  232. );
  233. } catch {
  234. try {
  235. continueBtn = await waitForElementByText('button', /继续|Continue/, 5000);
  236. } catch {
  237. throw new Error('Could not find "继续" button on OAuth consent page. URL: ' + location.href);
  238. }
  239. }
  240. await humanPause(350, 900);
  241. continueBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
  242. continueBtn.focus();
  243. log('Step 8: Found "继续" button and focused it. Please click it manually.');
  244. }
  245. // ============================================================
  246. // Step 5: Fill Name & Birthday / Age
  247. // ============================================================
  248. async function step5_fillNameBirthday(payload) {
  249. const { firstName, lastName, age, year, month, day } = payload;
  250. if (!firstName || !lastName) throw new Error('No name data provided.');
  251. const resolvedAge = age ?? (year ? new Date().getFullYear() - Number(year) : null);
  252. const hasBirthdayData = [year, month, day].every(value => value != null && !Number.isNaN(Number(value)));
  253. if (!hasBirthdayData && (resolvedAge == null || Number.isNaN(Number(resolvedAge)))) {
  254. throw new Error('No birthday or age data provided.');
  255. }
  256. const fullName = `${firstName} ${lastName}`;
  257. log(`Step 5: Filling name: ${fullName}`);
  258. // Actual DOM structure:
  259. // - Full name: <input name="name" placeholder="全名" type="text">
  260. // - Birthday: React Aria DateField or hidden input[name="birthday"]
  261. // - Age: <input name="age" type="text|number">
  262. // --- Full Name (single field, not first+last) ---
  263. let nameInput = null;
  264. try {
  265. nameInput = await waitForElement(
  266. 'input[name="name"], input[placeholder*="全名"], input[autocomplete="name"]',
  267. 10000
  268. );
  269. } catch {
  270. throw new Error('Could not find name input. URL: ' + location.href);
  271. }
  272. await humanPause(500, 1300);
  273. fillInput(nameInput, fullName);
  274. log(`Step 5: Name filled: ${fullName}`);
  275. let birthdayMode = false;
  276. let ageInput = null;
  277. for (let i = 0; i < 100; i++) {
  278. const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]');
  279. const monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]');
  280. const daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]');
  281. const hiddenBirthday = document.querySelector('input[name="birthday"]');
  282. ageInput = document.querySelector('input[name="age"]');
  283. if ((yearSpinner && monthSpinner && daySpinner) || hiddenBirthday) {
  284. birthdayMode = true;
  285. break;
  286. }
  287. if (ageInput) break;
  288. await sleep(100);
  289. }
  290. if (birthdayMode) {
  291. if (!hasBirthdayData) {
  292. throw new Error('Birthday field detected, but no birthday data provided.');
  293. }
  294. const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]');
  295. const monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]');
  296. const daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]');
  297. if (yearSpinner && monthSpinner && daySpinner) {
  298. log('Step 5: Birthday fields detected, filling birthday...');
  299. async function setSpinButton(el, value) {
  300. el.focus();
  301. await sleep(100);
  302. document.execCommand('selectAll', false, null);
  303. await sleep(50);
  304. const valueStr = String(value);
  305. for (const char of valueStr) {
  306. el.dispatchEvent(new KeyboardEvent('keydown', { key: char, code: `Digit${char}`, bubbles: true }));
  307. el.dispatchEvent(new KeyboardEvent('keypress', { key: char, code: `Digit${char}`, bubbles: true }));
  308. el.dispatchEvent(new InputEvent('beforeinput', { inputType: 'insertText', data: char, bubbles: true }));
  309. el.dispatchEvent(new InputEvent('input', { inputType: 'insertText', data: char, bubbles: true }));
  310. await sleep(50);
  311. }
  312. el.dispatchEvent(new KeyboardEvent('keyup', { key: 'Tab', code: 'Tab', bubbles: true }));
  313. el.blur();
  314. await sleep(100);
  315. }
  316. await humanPause(450, 1100);
  317. await setSpinButton(yearSpinner, year);
  318. await humanPause(250, 650);
  319. await setSpinButton(monthSpinner, String(month).padStart(2, '0'));
  320. await humanPause(250, 650);
  321. await setSpinButton(daySpinner, String(day).padStart(2, '0'));
  322. log(`Step 5: Birthday filled: ${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
  323. }
  324. const hiddenBirthday = document.querySelector('input[name="birthday"]');
  325. if (hiddenBirthday) {
  326. const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  327. hiddenBirthday.value = dateStr;
  328. hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true }));
  329. log(`Step 5: Hidden birthday input set: ${dateStr}`);
  330. }
  331. } else if (ageInput) {
  332. if (resolvedAge == null || Number.isNaN(Number(resolvedAge))) {
  333. throw new Error('Age field detected, but no age data provided.');
  334. }
  335. await humanPause(500, 1300);
  336. fillInput(ageInput, String(resolvedAge));
  337. log(`Step 5: Age filled: ${resolvedAge}`);
  338. } else {
  339. throw new Error('Could not find birthday or age input. URL: ' + location.href);
  340. }
  341. // Click "完成帐户创建" button
  342. await sleep(500);
  343. const completeBtn = document.querySelector('button[type="submit"]')
  344. || await waitForElementByText('button', /完成|create|continue|finish|done|agree/i, 5000).catch(() => null);
  345. // Report complete BEFORE submit (page navigates to add-phone after this)
  346. reportComplete(5);
  347. if (completeBtn) {
  348. await humanPause(500, 1300);
  349. simulateClick(completeBtn);
  350. log('Step 5: Clicked "完成帐户创建"');
  351. }
  352. }