ask-pro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +30 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/assets/ask-pro_logo.png +0 -0
- package/dist/bin/ask-pro-cli.js +507 -0
- package/dist/scripts/run-cli.js +27 -0
- package/dist/src/ask-pro/atomicWrite.js +26 -0
- package/dist/src/ask-pro/browserRunner.js +796 -0
- package/dist/src/ask-pro/responseZip.js +349 -0
- package/dist/src/ask-pro/session.js +662 -0
- package/dist/src/ask-pro/sessionControllerLease.js +64 -0
- package/dist/src/ask-pro/toon.js +26 -0
- package/dist/src/ask-pro/zip.js +85 -0
- package/dist/src/browser/actions/assistantResponse.js +1245 -0
- package/dist/src/browser/actions/attachmentDataTransfer.js +140 -0
- package/dist/src/browser/actions/attachments.js +1720 -0
- package/dist/src/browser/actions/composerSendReadiness.js +369 -0
- package/dist/src/browser/actions/domEvents.js +31 -0
- package/dist/src/browser/actions/inputGuard.js +52 -0
- package/dist/src/browser/actions/modelPickerDom.js +68 -0
- package/dist/src/browser/actions/modelSelection.js +576 -0
- package/dist/src/browser/actions/navigation.js +510 -0
- package/dist/src/browser/actions/promptComposer.js +824 -0
- package/dist/src/browser/actions/remoteFileTransfer.js +37 -0
- package/dist/src/browser/actions/thinkingStatus.js +408 -0
- package/dist/src/browser/actions/thinkingTime.js +635 -0
- package/dist/src/browser/actions/windowState.js +47 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptModelCatalog.js +321 -0
- package/dist/src/browser/chromeLifecycle.js +807 -0
- package/dist/src/browser/config.js +110 -0
- package/dist/src/browser/constants.js +85 -0
- package/dist/src/browser/cookies.js +191 -0
- package/dist/src/browser/detect.js +337 -0
- package/dist/src/browser/domDebug.js +72 -0
- package/dist/src/browser/errors.js +20 -0
- package/dist/src/browser/format.js +16 -0
- package/dist/src/browser/index.js +2631 -0
- package/dist/src/browser/language.js +97 -0
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +13 -0
- package/dist/src/browser/pageActions.js +5 -0
- package/dist/src/browser/profilePaths.js +282 -0
- package/dist/src/browser/profileState.js +413 -0
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +50 -0
- package/dist/src/browser/reattach.js +534 -0
- package/dist/src/browser/reattachHelpers.js +387 -0
- package/dist/src/browser/utils.js +122 -0
- package/dist/src/browserMode.js +1 -0
- package/dist/src/version.js +39 -0
- package/package.json +114 -0
- package/scripts/refresh-local-plugin.mjs +179 -0
- package/scripts/refresh-local-plugin.ps1 +93 -0
- package/skills/ask-pro/SKILL.md +181 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { CHATGPT_URL, DEFAULT_MODEL_STRATEGY, DEFAULT_MODEL_TARGET } from "./constants.js";
|
|
2
|
+
import { normalizeBrowserModelStrategy } from "./modelStrategy.js";
|
|
3
|
+
import { defaultAskProBrowserProfileDir } from "./profilePaths.js";
|
|
4
|
+
import { normalizeChatgptUrl } from "./utils.js";
|
|
5
|
+
export const DEFAULT_BROWSER_CONFIG = {
|
|
6
|
+
chromeProfile: null,
|
|
7
|
+
chromePath: null,
|
|
8
|
+
chromeCookiePath: null,
|
|
9
|
+
attachRunning: false,
|
|
10
|
+
browserTabRef: null,
|
|
11
|
+
url: CHATGPT_URL,
|
|
12
|
+
chatgptUrl: CHATGPT_URL,
|
|
13
|
+
timeoutMs: 1_200_000,
|
|
14
|
+
debugPort: null,
|
|
15
|
+
inputTimeoutMs: 60_000,
|
|
16
|
+
assistantRecheckDelayMs: 0,
|
|
17
|
+
assistantRecheckTimeoutMs: 120_000,
|
|
18
|
+
profileLockTimeoutMs: 300_000,
|
|
19
|
+
autoReattachDelayMs: 0,
|
|
20
|
+
autoReattachIntervalMs: 0,
|
|
21
|
+
autoReattachTimeoutMs: 120_000,
|
|
22
|
+
cookieSync: true,
|
|
23
|
+
cookieNames: null,
|
|
24
|
+
cookieSyncWaitMs: 0,
|
|
25
|
+
inlineCookies: null,
|
|
26
|
+
inlineCookiesSource: null,
|
|
27
|
+
headless: false,
|
|
28
|
+
keepBrowser: false,
|
|
29
|
+
hideWindow: false,
|
|
30
|
+
startMinimized: false,
|
|
31
|
+
desiredModel: DEFAULT_MODEL_TARGET,
|
|
32
|
+
modelStrategy: DEFAULT_MODEL_STRATEGY,
|
|
33
|
+
debug: false,
|
|
34
|
+
allowCookieErrors: false,
|
|
35
|
+
remoteChrome: null,
|
|
36
|
+
remoteChromeBrowserWSEndpoint: null,
|
|
37
|
+
remoteChromeProfileRoot: null,
|
|
38
|
+
manualLogin: false,
|
|
39
|
+
manualLoginProfileDir: null,
|
|
40
|
+
manualLoginCookieSync: false,
|
|
41
|
+
manualLoginWaitMs: 20 * 60_000,
|
|
42
|
+
acceptLanguage: "en-US,en",
|
|
43
|
+
};
|
|
44
|
+
export function resolveBrowserConfig(config) {
|
|
45
|
+
const debugPortEnv = parseDebugPort(process.env.ASK_PRO_BROWSER_PORT ?? process.env.ASK_PRO_BROWSER_DEBUG_PORT);
|
|
46
|
+
const envAllowCookieErrors = (process.env.ASK_PRO_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim().toLowerCase() === "true" ||
|
|
47
|
+
(process.env.ASK_PRO_BROWSER_ALLOW_COOKIE_ERRORS ?? "").trim() === "1";
|
|
48
|
+
const rawUrl = config?.chatgptUrl ?? config?.url ?? DEFAULT_BROWSER_CONFIG.url;
|
|
49
|
+
const normalizedUrl = normalizeChatgptUrl(rawUrl ?? DEFAULT_BROWSER_CONFIG.url, DEFAULT_BROWSER_CONFIG.url);
|
|
50
|
+
const desiredModel = config?.desiredModel ?? DEFAULT_BROWSER_CONFIG.desiredModel ?? DEFAULT_MODEL_TARGET;
|
|
51
|
+
const modelStrategy = normalizeBrowserModelStrategy(config?.modelStrategy) ??
|
|
52
|
+
DEFAULT_BROWSER_CONFIG.modelStrategy ??
|
|
53
|
+
DEFAULT_MODEL_STRATEGY;
|
|
54
|
+
const isWindows = process.platform === "win32";
|
|
55
|
+
const manualLogin = config?.manualLogin ?? (isWindows ? true : DEFAULT_BROWSER_CONFIG.manualLogin);
|
|
56
|
+
const cookieSyncDefault = isWindows ? false : DEFAULT_BROWSER_CONFIG.cookieSync;
|
|
57
|
+
const resolvedProfileDir = config?.manualLoginProfileDir ??
|
|
58
|
+
process.env.ASK_PRO_BROWSER_PROFILE_DIR ??
|
|
59
|
+
defaultAskProBrowserProfileDir();
|
|
60
|
+
return {
|
|
61
|
+
...DEFAULT_BROWSER_CONFIG,
|
|
62
|
+
...config,
|
|
63
|
+
url: normalizedUrl,
|
|
64
|
+
chatgptUrl: normalizedUrl,
|
|
65
|
+
timeoutMs: config?.timeoutMs ?? DEFAULT_BROWSER_CONFIG.timeoutMs,
|
|
66
|
+
debugPort: config?.debugPort ?? debugPortEnv ?? DEFAULT_BROWSER_CONFIG.debugPort,
|
|
67
|
+
inputTimeoutMs: config?.inputTimeoutMs ?? DEFAULT_BROWSER_CONFIG.inputTimeoutMs,
|
|
68
|
+
assistantRecheckDelayMs: config?.assistantRecheckDelayMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckDelayMs,
|
|
69
|
+
assistantRecheckTimeoutMs: config?.assistantRecheckTimeoutMs ?? DEFAULT_BROWSER_CONFIG.assistantRecheckTimeoutMs,
|
|
70
|
+
profileLockTimeoutMs: config?.profileLockTimeoutMs ?? DEFAULT_BROWSER_CONFIG.profileLockTimeoutMs,
|
|
71
|
+
autoReattachDelayMs: config?.autoReattachDelayMs ?? DEFAULT_BROWSER_CONFIG.autoReattachDelayMs,
|
|
72
|
+
autoReattachIntervalMs: config?.autoReattachIntervalMs ?? DEFAULT_BROWSER_CONFIG.autoReattachIntervalMs,
|
|
73
|
+
autoReattachTimeoutMs: config?.autoReattachTimeoutMs ?? DEFAULT_BROWSER_CONFIG.autoReattachTimeoutMs,
|
|
74
|
+
cookieSync: config?.cookieSync ?? cookieSyncDefault,
|
|
75
|
+
cookieNames: config?.cookieNames ?? DEFAULT_BROWSER_CONFIG.cookieNames,
|
|
76
|
+
cookieSyncWaitMs: config?.cookieSyncWaitMs ?? DEFAULT_BROWSER_CONFIG.cookieSyncWaitMs,
|
|
77
|
+
inlineCookies: config?.inlineCookies ?? DEFAULT_BROWSER_CONFIG.inlineCookies,
|
|
78
|
+
inlineCookiesSource: config?.inlineCookiesSource ?? DEFAULT_BROWSER_CONFIG.inlineCookiesSource,
|
|
79
|
+
headless: config?.headless ?? DEFAULT_BROWSER_CONFIG.headless,
|
|
80
|
+
keepBrowser: config?.keepBrowser ?? DEFAULT_BROWSER_CONFIG.keepBrowser,
|
|
81
|
+
hideWindow: config?.hideWindow ?? DEFAULT_BROWSER_CONFIG.hideWindow,
|
|
82
|
+
startMinimized: config?.startMinimized ?? DEFAULT_BROWSER_CONFIG.startMinimized,
|
|
83
|
+
desiredModel,
|
|
84
|
+
modelStrategy,
|
|
85
|
+
chromeProfile: config?.chromeProfile ?? DEFAULT_BROWSER_CONFIG.chromeProfile,
|
|
86
|
+
chromePath: config?.chromePath ?? DEFAULT_BROWSER_CONFIG.chromePath,
|
|
87
|
+
chromeCookiePath: config?.chromeCookiePath ?? DEFAULT_BROWSER_CONFIG.chromeCookiePath,
|
|
88
|
+
attachRunning: config?.attachRunning ?? DEFAULT_BROWSER_CONFIG.attachRunning,
|
|
89
|
+
browserTabRef: config?.browserTabRef ?? DEFAULT_BROWSER_CONFIG.browserTabRef,
|
|
90
|
+
debug: config?.debug ?? DEFAULT_BROWSER_CONFIG.debug,
|
|
91
|
+
allowCookieErrors: config?.allowCookieErrors ?? envAllowCookieErrors ?? DEFAULT_BROWSER_CONFIG.allowCookieErrors,
|
|
92
|
+
remoteChromeBrowserWSEndpoint: config?.remoteChromeBrowserWSEndpoint ?? DEFAULT_BROWSER_CONFIG.remoteChromeBrowserWSEndpoint,
|
|
93
|
+
remoteChromeProfileRoot: config?.remoteChromeProfileRoot ?? DEFAULT_BROWSER_CONFIG.remoteChromeProfileRoot,
|
|
94
|
+
thinkingTime: config?.thinkingTime,
|
|
95
|
+
manualLogin,
|
|
96
|
+
manualLoginProfileDir: manualLogin ? resolvedProfileDir : null,
|
|
97
|
+
manualLoginCookieSync: config?.manualLoginCookieSync ?? DEFAULT_BROWSER_CONFIG.manualLoginCookieSync,
|
|
98
|
+
manualLoginWaitMs: config?.manualLoginWaitMs ?? DEFAULT_BROWSER_CONFIG.manualLoginWaitMs,
|
|
99
|
+
acceptLanguage: config?.acceptLanguage ?? DEFAULT_BROWSER_CONFIG.acceptLanguage,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function parseDebugPort(raw) {
|
|
103
|
+
if (!raw)
|
|
104
|
+
return null;
|
|
105
|
+
const value = Number.parseInt(raw, 10);
|
|
106
|
+
if (!Number.isFinite(value) || value <= 0 || value > 65535) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DEFAULT_CHATGPT_BROWSER_MODEL_LABEL } from "./chatgptModelCatalog.js";
|
|
2
|
+
export const CHATGPT_URL = "https://chatgpt.com/";
|
|
3
|
+
export const DEFAULT_MODEL_TARGET = DEFAULT_CHATGPT_BROWSER_MODEL_LABEL;
|
|
4
|
+
export const DEFAULT_MODEL_STRATEGY = "select";
|
|
5
|
+
export const COOKIE_URLS = [
|
|
6
|
+
"https://chatgpt.com",
|
|
7
|
+
"https://chat.openai.com",
|
|
8
|
+
"https://atlas.openai.com",
|
|
9
|
+
];
|
|
10
|
+
export const INPUT_SELECTORS = [
|
|
11
|
+
'textarea[data-id="prompt-textarea"]',
|
|
12
|
+
'textarea[placeholder*="Send a message"]',
|
|
13
|
+
'textarea[aria-label="Chat with ChatGPT"]',
|
|
14
|
+
'textarea[aria-label="Message ChatGPT"]',
|
|
15
|
+
"textarea:not([disabled])",
|
|
16
|
+
'textarea[name="prompt-textarea"]',
|
|
17
|
+
"#prompt-textarea",
|
|
18
|
+
".ProseMirror",
|
|
19
|
+
'[contenteditable="true"][role="textbox"]',
|
|
20
|
+
'[contenteditable="true"][data-virtualkeyboard="true"]',
|
|
21
|
+
];
|
|
22
|
+
export const ANSWER_SELECTORS = [
|
|
23
|
+
'article[data-testid^="conversation-turn"][data-message-author-role="assistant"]',
|
|
24
|
+
'article[data-testid^="conversation-turn"][data-turn="assistant"]',
|
|
25
|
+
'article[data-testid^="conversation-turn"] [data-message-author-role="assistant"]',
|
|
26
|
+
'article[data-testid^="conversation-turn"] [data-turn="assistant"]',
|
|
27
|
+
'article[data-testid^="conversation-turn"] .markdown',
|
|
28
|
+
'[data-message-author-role="assistant"] .markdown',
|
|
29
|
+
'[data-turn="assistant"] .markdown',
|
|
30
|
+
'[data-message-author-role="assistant"]',
|
|
31
|
+
'[data-turn="assistant"]',
|
|
32
|
+
];
|
|
33
|
+
export const CONVERSATION_TURN_SELECTOR = 'article[data-testid^="conversation-turn"], div[data-testid^="conversation-turn"], section[data-testid^="conversation-turn"], ' +
|
|
34
|
+
"article[data-message-author-role], div[data-message-author-role], section[data-message-author-role], " +
|
|
35
|
+
"article[data-turn], div[data-turn], section[data-turn]";
|
|
36
|
+
export const ASSISTANT_ROLE_SELECTOR = '[data-message-author-role="assistant"], [data-turn="assistant"]';
|
|
37
|
+
export const CLOUDFLARE_SCRIPT_SELECTOR = 'script[src*="/challenge-platform/"]';
|
|
38
|
+
export const CLOUDFLARE_TITLE = "just a moment";
|
|
39
|
+
export const PROMPT_PRIMARY_SELECTOR = "#prompt-textarea";
|
|
40
|
+
export const PROMPT_FALLBACK_SELECTOR = 'textarea[name="prompt-textarea"]';
|
|
41
|
+
export const FILE_INPUT_SELECTORS = [
|
|
42
|
+
'form input[type="file"]:not([accept])',
|
|
43
|
+
'input[type="file"][multiple]:not([accept])',
|
|
44
|
+
'input[type="file"][multiple]',
|
|
45
|
+
'input[type="file"]:not([accept])',
|
|
46
|
+
'form input[type="file"][accept]',
|
|
47
|
+
'input[type="file"][accept]',
|
|
48
|
+
'input[type="file"]',
|
|
49
|
+
'input[type="file"][data-testid*="file"]',
|
|
50
|
+
];
|
|
51
|
+
// Legacy single selectors kept for compatibility with older call-sites
|
|
52
|
+
export const FILE_INPUT_SELECTOR = FILE_INPUT_SELECTORS[0];
|
|
53
|
+
export const GENERIC_FILE_INPUT_SELECTOR = FILE_INPUT_SELECTORS[3];
|
|
54
|
+
export const MENU_CONTAINER_SELECTOR = '[role="menu"], [data-radix-collection-root]';
|
|
55
|
+
export const MENU_ITEM_SELECTOR = 'button, [role="menuitem"], [role="menuitemradio"], [data-testid*="model-switcher-"]';
|
|
56
|
+
export const UPLOAD_STATUS_SELECTORS = [
|
|
57
|
+
'[data-testid*="upload"]',
|
|
58
|
+
'[data-testid*="attachment"]',
|
|
59
|
+
'[data-testid*="progress"]',
|
|
60
|
+
'[data-state="loading"]',
|
|
61
|
+
'[data-state="uploading"]',
|
|
62
|
+
'[data-state="pending"]',
|
|
63
|
+
'[aria-live="polite"]',
|
|
64
|
+
'[aria-live="assertive"]',
|
|
65
|
+
];
|
|
66
|
+
export const STOP_BUTTON_SELECTOR = '[data-testid="stop-button"]';
|
|
67
|
+
export const SEND_BUTTON_SELECTORS = [
|
|
68
|
+
'button[data-testid="send-button"]',
|
|
69
|
+
'button[data-testid*="composer-send"]',
|
|
70
|
+
'form button[type="submit"]',
|
|
71
|
+
'button[type="submit"][data-testid*="send"]',
|
|
72
|
+
'button[aria-label*="Send"]',
|
|
73
|
+
];
|
|
74
|
+
export const SEND_BUTTON_SELECTOR = SEND_BUTTON_SELECTORS[0];
|
|
75
|
+
export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"], button.__composer-pill';
|
|
76
|
+
export const COPY_BUTTON_SELECTORS = [
|
|
77
|
+
'button[data-testid="copy-turn-action-button"]',
|
|
78
|
+
'button[aria-label="Copy response"]',
|
|
79
|
+
'button[aria-label="Response copied"]',
|
|
80
|
+
'button[aria-label="Copy message"]',
|
|
81
|
+
'button[aria-label="Message copied"]',
|
|
82
|
+
];
|
|
83
|
+
export const COPY_BUTTON_SELECTOR = COPY_BUTTON_SELECTORS.join(", ");
|
|
84
|
+
// Action buttons that only appear once a turn has finished rendering.
|
|
85
|
+
export const FINISHED_ACTIONS_SELECTOR = `${COPY_BUTTON_SELECTOR}, button[data-testid="good-response-turn-action-button"], button[data-testid="bad-response-turn-action-button"], button[aria-label="Share"]`;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { COOKIE_URLS } from "./constants.js";
|
|
2
|
+
import { delay } from "./utils.js";
|
|
3
|
+
import { getCookies } from "@steipete/sweet-cookie";
|
|
4
|
+
export class ChromeCookieSyncError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export async function syncCookies(Network, url, profile, logger, options = {}) {
|
|
7
|
+
const { allowErrors = false, filterNames, inlineCookies, cookiePath, waitMs = 0 } = options;
|
|
8
|
+
try {
|
|
9
|
+
// Learned: inline cookies are the most deterministic (avoid Keychain + profile ambiguity).
|
|
10
|
+
const cookies = inlineCookies?.length
|
|
11
|
+
? normalizeInlineCookies(inlineCookies, new URL(url).hostname)
|
|
12
|
+
: await readChromeCookiesWithWait(url, profile, filterNames ?? undefined, cookiePath ?? undefined, waitMs, logger);
|
|
13
|
+
if (!cookies.length) {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
let applied = 0;
|
|
17
|
+
for (const cookie of cookies) {
|
|
18
|
+
const cookieWithUrl = attachUrl(cookie, url);
|
|
19
|
+
try {
|
|
20
|
+
// Learned: CDP will silently drop cookies without a url; always attach one.
|
|
21
|
+
const result = await Network.setCookie(cookieWithUrl);
|
|
22
|
+
if (result?.success) {
|
|
23
|
+
applied += 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
logger(`Failed to set cookie ${cookie.name}: ${message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return applied;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
if (allowErrors) {
|
|
36
|
+
logger(`Cookie sync failed (continuing with override): ${message}`);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
throw error instanceof ChromeCookieSyncError ? error : new ChromeCookieSyncError(message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function readChromeCookiesWithWait(url, profile, filterNames, cookiePath, waitMs, logger) {
|
|
43
|
+
if (waitMs <= 0) {
|
|
44
|
+
return readChromeCookies(url, profile, filterNames, cookiePath);
|
|
45
|
+
}
|
|
46
|
+
let cookies = [];
|
|
47
|
+
let firstError;
|
|
48
|
+
try {
|
|
49
|
+
cookies = await readChromeCookies(url, profile, filterNames, cookiePath);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
firstError = error;
|
|
53
|
+
}
|
|
54
|
+
if (cookies.length > 0 && !firstError) {
|
|
55
|
+
return cookies;
|
|
56
|
+
}
|
|
57
|
+
const waitLabel = waitMs >= 1000 ? `${Math.round(waitMs / 1000)}s` : `${waitMs}ms`;
|
|
58
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError ?? "");
|
|
59
|
+
if (firstError) {
|
|
60
|
+
logger(`[cookies] Cookie read failed (${message}); waiting ${waitLabel} then retrying once.`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
logger(`[cookies] No cookies found; waiting ${waitLabel} then retrying once.`);
|
|
64
|
+
}
|
|
65
|
+
await delay(waitMs);
|
|
66
|
+
return readChromeCookies(url, profile, filterNames, cookiePath);
|
|
67
|
+
}
|
|
68
|
+
async function readChromeCookies(url, profile, filterNames, cookiePath) {
|
|
69
|
+
const origins = Array.from(new Set([stripQuery(url), ...COOKIE_URLS]));
|
|
70
|
+
const chromeProfile = cookiePath ?? profile ?? undefined;
|
|
71
|
+
const timeoutMs = readDuration("ASK_PRO_COOKIE_LOAD_TIMEOUT_MS", 5_000);
|
|
72
|
+
// Learned: read from multiple origins to capture auth cookies that land on chat.openai.com + atlas.
|
|
73
|
+
const { cookies, warnings } = await getCookies({
|
|
74
|
+
url,
|
|
75
|
+
origins,
|
|
76
|
+
names: filterNames?.length ? filterNames : undefined,
|
|
77
|
+
browsers: ["chrome"],
|
|
78
|
+
mode: "merge",
|
|
79
|
+
chromeProfile,
|
|
80
|
+
timeoutMs,
|
|
81
|
+
});
|
|
82
|
+
if (process.env.ASK_PRO_DEBUG_COOKIES === "1" && warnings.length) {
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.error(`[cookies] sweet-cookie warnings:\n- ${warnings.join("\n- ")}`);
|
|
85
|
+
}
|
|
86
|
+
const merged = new Map();
|
|
87
|
+
for (const cookie of cookies) {
|
|
88
|
+
const normalized = toCdpCookie(cookie);
|
|
89
|
+
if (!normalized)
|
|
90
|
+
continue;
|
|
91
|
+
const key = `${normalized.domain ?? ""}:${normalized.name}`;
|
|
92
|
+
if (!merged.has(key))
|
|
93
|
+
merged.set(key, normalized);
|
|
94
|
+
}
|
|
95
|
+
return Array.from(merged.values());
|
|
96
|
+
}
|
|
97
|
+
function normalizeInlineCookies(rawCookies, fallbackHost) {
|
|
98
|
+
const merged = new Map();
|
|
99
|
+
for (const cookie of rawCookies) {
|
|
100
|
+
if (!cookie?.name)
|
|
101
|
+
continue;
|
|
102
|
+
// Learned: inline cookies may omit url/domain; default to current host with a safe path.
|
|
103
|
+
const normalized = {
|
|
104
|
+
name: cookie.name,
|
|
105
|
+
value: cookie.value ?? "",
|
|
106
|
+
url: cookie.url,
|
|
107
|
+
domain: cookie.domain ?? fallbackHost,
|
|
108
|
+
path: cookie.path ?? "/",
|
|
109
|
+
expires: normalizeExpiration(cookie.expires),
|
|
110
|
+
secure: cookie.secure ?? true,
|
|
111
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
112
|
+
sameSite: cookie.sameSite,
|
|
113
|
+
};
|
|
114
|
+
const key = `${normalized.domain ?? fallbackHost}:${normalized.name}`;
|
|
115
|
+
if (!merged.has(key)) {
|
|
116
|
+
merged.set(key, normalized);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Array.from(merged.values());
|
|
120
|
+
}
|
|
121
|
+
function toCdpCookie(cookie) {
|
|
122
|
+
if (!cookie?.name)
|
|
123
|
+
return null;
|
|
124
|
+
const out = {
|
|
125
|
+
name: cookie.name,
|
|
126
|
+
value: cookie.value,
|
|
127
|
+
domain: cookie.domain,
|
|
128
|
+
path: cookie.path ?? "/",
|
|
129
|
+
secure: cookie.secure ?? true,
|
|
130
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
131
|
+
};
|
|
132
|
+
if (typeof cookie.expires === "number")
|
|
133
|
+
out.expires = cookie.expires;
|
|
134
|
+
if (cookie.sameSite === "Lax" || cookie.sameSite === "Strict" || cookie.sameSite === "None") {
|
|
135
|
+
out.sameSite = cookie.sameSite;
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function attachUrl(cookie, fallbackUrl) {
|
|
140
|
+
const cookieWithUrl = { ...cookie };
|
|
141
|
+
if (!cookieWithUrl.url) {
|
|
142
|
+
if (!cookieWithUrl.domain || cookieWithUrl.domain === "localhost") {
|
|
143
|
+
cookieWithUrl.url = fallbackUrl;
|
|
144
|
+
}
|
|
145
|
+
else if (!cookieWithUrl.domain.startsWith(".")) {
|
|
146
|
+
cookieWithUrl.url = `https://${cookieWithUrl.domain}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// When url is present, let Chrome derive the host from it; keeping domain can trigger CDP sanitization errors.
|
|
150
|
+
if (cookieWithUrl.url) {
|
|
151
|
+
// Learned: CDP rejects cookies with both url + domain in some cases; drop domain to avoid failures.
|
|
152
|
+
delete cookieWithUrl.domain;
|
|
153
|
+
}
|
|
154
|
+
return cookieWithUrl;
|
|
155
|
+
}
|
|
156
|
+
function stripQuery(url) {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = new URL(url);
|
|
159
|
+
parsed.hash = "";
|
|
160
|
+
parsed.search = "";
|
|
161
|
+
return parsed.toString();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return url;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function normalizeExpiration(expires) {
|
|
168
|
+
if (!expires || Number.isNaN(expires)) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const value = Number(expires);
|
|
172
|
+
if (value <= 0) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
if (value > 1_000_000_000_000) {
|
|
176
|
+
// Learned: Chrome may store WebKit microseconds since 1601; convert to Unix seconds.
|
|
177
|
+
return Math.round(value / 1_000_000 - 11644473600);
|
|
178
|
+
}
|
|
179
|
+
if (value > 1_000_000_000) {
|
|
180
|
+
// Likely milliseconds; normalize to seconds for CDP.
|
|
181
|
+
return Math.round(value / 1000);
|
|
182
|
+
}
|
|
183
|
+
return Math.round(value);
|
|
184
|
+
}
|
|
185
|
+
function readDuration(envKey, defaultValueMs) {
|
|
186
|
+
const raw = process.env[envKey];
|
|
187
|
+
if (!raw)
|
|
188
|
+
return defaultValueMs;
|
|
189
|
+
const value = Number.parseInt(raw, 10);
|
|
190
|
+
return Number.isFinite(value) && value > 0 ? value : defaultValueMs;
|
|
191
|
+
}
|