get-plus-link.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // background/steps/get-plus-link.js — Step 6: Get ChatGPT Plus Stripe checkout link
  2. (function attachBackgroundStep11(root, factory) {
  3. root.MultiPageBackgroundStep11 = factory();
  4. })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep11Module() {
  5. const PAYURL_CHECKOUT_ENDPOINT = 'https://payurl.ark2.cn/api/checkout';
  6. const PAYURL_CHECKOUT_MAX_ATTEMPTS = 5;
  7. const PAYURL_CHECKOUT_RETRY_DELAY_MS = 1200;
  8. function createStep11Executor(deps = {}) {
  9. const {
  10. addLog,
  11. chrome,
  12. completeStepFromBackground,
  13. getTabId,
  14. reuseOrCreateTab,
  15. waitForTabStableComplete = null,
  16. } = deps;
  17. async function executeStep11() {
  18. await addLog('步骤 6:正在获取 ChatGPT Plus 订阅链接...');
  19. const tabId = await getTabId('signup-page');
  20. if (!tabId) {
  21. throw new Error('未找到 ChatGPT 标签页,请先打开 chatgpt.com');
  22. }
  23. await waitForChatGptLoginPageStable(tabId, {
  24. addLog,
  25. waitForTabStableComplete,
  26. });
  27. const accessToken = await getChatGptAccessToken(tabId, chrome, addLog);
  28. if (!accessToken) {
  29. throw new Error('未获取到 accessToken,请确认已登录 ChatGPT');
  30. }
  31. await addLog('已获取 ChatGPT Session Token,正在请求 Plus 长链...');
  32. const result = await requestPayurlCheckoutWithRetry(accessToken, addLog);
  33. if (!result.hostedUrl) {
  34. throw new Error('未从 payurl.ark2.cn 响应中解析到 Plus 长链');
  35. }
  36. await addLog(`已获取 Plus 链接,正在跳转到 Stripe 结账页面...`);
  37. await addLog(`Session ID: ${result.checkoutSessionId || '未知'}`);
  38. // Navigate to the Stripe checkout URL
  39. await reuseOrCreateTab('signup-page', result.hostedUrl, {
  40. reloadIfSameUrl: false,
  41. });
  42. await completeStepFromBackground(6, {
  43. checkoutUrl: result.hostedUrl,
  44. checkoutSessionId: result.checkoutSessionId,
  45. chatgptCheckoutUrl: result.chatgptCheckoutUrl,
  46. openaiPayUrl: result.openaiPayUrl,
  47. });
  48. }
  49. return { executeStep11 };
  50. }
  51. async function waitForChatGptLoginPageStable(tabId, deps = {}) {
  52. const {
  53. addLog,
  54. waitForTabStableComplete,
  55. } = deps;
  56. if (typeof waitForTabStableComplete !== 'function') {
  57. return null;
  58. }
  59. await addLog('步骤 6:正在等待步骤 5 登录后的 ChatGPT 页面稳定,再读取 Session...', 'info');
  60. const startedAt = Date.now();
  61. let lastTab = null;
  62. while (Date.now() - startedAt < 45000) {
  63. lastTab = await waitForTabStableComplete(tabId, {
  64. timeoutMs: 6000,
  65. retryDelayMs: 300,
  66. stableMs: 1500,
  67. initialDelayMs: 600,
  68. });
  69. if (!lastTab) {
  70. throw new Error('步骤 6:ChatGPT 标签页已关闭,无法获取 Session。');
  71. }
  72. const url = String(lastTab.url || '');
  73. if (isChatGptWebUrl(url)) {
  74. await addLog('步骤 6:ChatGPT 页面已稳定,开始读取 Session。', 'info');
  75. return lastTab;
  76. }
  77. await sleep(500);
  78. }
  79. const lastUrl = String(lastTab?.url || '');
  80. await addLog(
  81. `步骤 6:等待 ChatGPT 页面稳定超时,最后页面为 ${lastUrl || 'unknown'},将继续尝试读取 Session。`,
  82. 'warn'
  83. );
  84. return lastTab;
  85. }
  86. function isChatGptWebUrl(rawUrl = '') {
  87. try {
  88. const parsed = new URL(String(rawUrl || ''));
  89. return /(?:^|\.)chatgpt\.com$/i.test(parsed.hostname);
  90. } catch {
  91. return false;
  92. }
  93. }
  94. async function getChatGptAccessToken(tabId, chrome, addLog) {
  95. try {
  96. return await fetchAccessTokenFromSessionEndpoint();
  97. } catch (error) {
  98. await addLog(`后台获取 ChatGPT Session Token 失败,尝试在页面上下文重试:${error.message}`, 'warn');
  99. }
  100. const results = await chrome.scripting.executeScript({
  101. target: { tabId },
  102. func: fetchAccessTokenInPage,
  103. });
  104. const tokenResult = results?.[0]?.result;
  105. if (!tokenResult || tokenResult.error) {
  106. throw new Error(tokenResult?.error || '获取 ChatGPT Session Token 失败,请确认已登录 ChatGPT');
  107. }
  108. return String(tokenResult.accessToken || '').trim();
  109. }
  110. async function fetchAccessTokenFromSessionEndpoint() {
  111. const sessionResp = await fetch('https://chatgpt.com/api/auth/session', {
  112. credentials: 'include',
  113. headers: {
  114. 'Accept': 'application/json',
  115. },
  116. });
  117. const text = await sessionResp.text();
  118. let session = null;
  119. try {
  120. session = text ? JSON.parse(text) : null;
  121. } catch (error) {
  122. throw new Error(`Session 响应不是有效 JSON:${text.slice(0, 300)}`);
  123. }
  124. if (!sessionResp.ok) {
  125. throw new Error(`Session 请求失败,HTTP ${sessionResp.status}: ${JSON.stringify(session)}`);
  126. }
  127. const accessToken = String(session?.accessToken || '').trim();
  128. if (!accessToken) {
  129. throw new Error('Session 响应中没有 accessToken');
  130. }
  131. return accessToken;
  132. }
  133. /**
  134. * This function is executed in the page context via chrome.scripting.executeScript.
  135. * It has access to the page's cookies and can make same-origin requests.
  136. */
  137. async function fetchAccessTokenInPage() {
  138. try {
  139. const sessionResp = await fetch('https://chatgpt.com/api/auth/session', {
  140. credentials: 'include',
  141. });
  142. const session = await sessionResp.json();
  143. const accessToken = session?.accessToken;
  144. if (!accessToken) {
  145. return { error: '未获取到 accessToken,请确认已登录 ChatGPT' };
  146. }
  147. return { accessToken };
  148. } catch (e) {
  149. return { error: e.message };
  150. }
  151. }
  152. async function requestPayurlCheckoutWithRetry(accessToken, addLog) {
  153. let lastError = null;
  154. for (let attempt = 1; attempt <= PAYURL_CHECKOUT_MAX_ATTEMPTS; attempt += 1) {
  155. try {
  156. if (attempt > 1) {
  157. await addLog(`Plus 长链获取失败,正在重试 ${attempt}/${PAYURL_CHECKOUT_MAX_ATTEMPTS}...`, 'warn');
  158. await sleep(PAYURL_CHECKOUT_RETRY_DELAY_MS);
  159. }
  160. return await requestPayurlCheckout(accessToken);
  161. } catch (error) {
  162. lastError = error;
  163. await addLog(`Plus 长链获取失败(${attempt}/${PAYURL_CHECKOUT_MAX_ATTEMPTS}):${error.message}`, 'warn');
  164. }
  165. }
  166. throw new Error(`Plus 长链获取连续失败 ${PAYURL_CHECKOUT_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`);
  167. }
  168. async function requestPayurlCheckout(accessToken) {
  169. const payload = {
  170. token: accessToken,
  171. plan: 'plus',
  172. checkout_ui_mode: 'hosted',
  173. ui_language: 'en',
  174. country: 'US',
  175. currency: 'USD',
  176. proxy: '',
  177. use_promo: true,
  178. promo_code: 'STRIPEATLASGPT4BIZ050126',
  179. workspace_name: 'linux-do',
  180. seat_quantity: 2,
  181. };
  182. const response = await fetch(PAYURL_CHECKOUT_ENDPOINT, {
  183. method: 'POST',
  184. headers: {
  185. 'Accept': '*/*',
  186. 'Accept-Language': 'zh-CN,zh;q=0.9',
  187. 'Content-Type': 'application/json',
  188. 'DNT': '1',
  189. 'Origin': 'https://payurl.ark2.cn',
  190. 'Referer': 'https://payurl.ark2.cn/',
  191. },
  192. body: JSON.stringify(payload),
  193. });
  194. const responseText = await response.text();
  195. let data = null;
  196. try {
  197. data = responseText ? JSON.parse(responseText) : null;
  198. } catch (error) {
  199. throw new Error(`payurl 响应不是有效 JSON:${responseText.slice(0, 300)}`);
  200. }
  201. if (!response.ok) {
  202. throw new Error(`payurl 请求失败,HTTP ${response.status}: ${JSON.stringify(data)}`);
  203. }
  204. const hostedUrl = data?.url || data?.openai_payurl || data?.chatgpt_checkout_url || '';
  205. if (!hostedUrl) {
  206. throw new Error(`payurl 响应中未找到 url/openai_payurl/chatgpt_checkout_url:${JSON.stringify(data)}`);
  207. }
  208. return {
  209. hostedUrl,
  210. checkoutSessionId: data.checkout_session_id || '',
  211. chatgptCheckoutUrl: data.chatgpt_checkout_url || '',
  212. openaiPayUrl: data.openai_payurl || data.url || '',
  213. };
  214. }
  215. function sleep(ms) {
  216. return new Promise((resolve) => setTimeout(resolve, ms));
  217. }
  218. return { createStep11Executor };
  219. });