返回 last30days-skill
cookies.js
1 /**
2 * Browser cookie extraction for Twitter authentication.
3 * Delegates to @steipete/sweet-cookie for Safari/Chrome/Firefox reads.
4 */
5 const TWITTER_COOKIE_NAMES = ['auth_token', 'ct0'];
6 const TWITTER_URL = 'https://x.com/';
7 const TWITTER_ORIGINS = ['https://x.com/', 'https://twitter.com/'];
8 const DEFAULT_COOKIE_TIMEOUT_MS = 30_000;
9 async function loadSweetCookie() {
10 return import('@steipete/sweet-cookie');
11 }
12 function normalizeValue(value) {
13 if (typeof value !== 'string') {
14 return null;
15 }
16 const trimmed = value.trim();
17 return trimmed.length > 0 ? trimmed : null;
18 }
19 function envFlagEnabled(name) {
20 const value = normalizeValue(process.env[name]);
21 if (!value) {
22 return false;
23 }
24 return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
25 }
26 function cookieHeader(authToken, ct0) {
27 return `auth_token=${authToken}; ct0=${ct0}`;
28 }
29 function buildEmpty() {
30 return { authToken: null, ct0: null, cookieHeader: null, source: null };
31 }
32 function readEnvCookie(cookies, keys, field) {
33 if (cookies[field]) {
34 return;
35 }
36 for (const key of keys) {
37 const value = normalizeValue(process.env[key]);
38 if (!value) {
39 continue;
40 }
41 cookies[field] = value;
42 if (!cookies.source) {
43 cookies.source = `env ${key}`;
44 }
45 break;
46 }
47 }
48 function resolveSources(cookieSource) {
49 if (Array.isArray(cookieSource)) {
50 return cookieSource;
51 }
52 if (cookieSource) {
53 return [cookieSource];
54 }
55 return ['safari', 'chrome', 'firefox'];
56 }
57 function labelForSource(source, profile) {
58 if (source === 'safari') {
59 return 'Safari';
60 }
61 if (source === 'chrome') {
62 return profile ? `Chrome profile "${profile}"` : 'Chrome default profile';
63 }
64 return profile ? `Firefox profile "${profile}"` : 'Firefox default profile';
65 }
66 function pickCookieValue(cookies, name) {
67 const matches = cookies.filter((c) => c?.name === name && typeof c.value === 'string');
68 if (matches.length === 0) {
69 return null;
70 }
71 const preferred = matches.find((c) => (c.domain ?? '').endsWith('x.com'));
72 if (preferred?.value) {
73 return preferred.value;
74 }
75 const twitter = matches.find((c) => (c.domain ?? '').endsWith('twitter.com'));
76 if (twitter?.value) {
77 return twitter.value;
78 }
79 return matches[0]?.value ?? null;
80 }
81 async function readTwitterCookiesFromBrowser(options) {
82 const warnings = [];
83 const out = buildEmpty();
84 const { getCookies } = options;
85 const { cookies, warnings: providerWarnings } = await getCookies({
86 url: TWITTER_URL,
87 origins: TWITTER_ORIGINS,
88 names: [...TWITTER_COOKIE_NAMES],
89 browsers: [options.source],
90 mode: 'merge',
91 chromeProfile: options.chromeProfile,
92 firefoxProfile: options.firefoxProfile,
93 timeoutMs: options.cookieTimeoutMs,
94 });
95 warnings.push(...providerWarnings);
96 const authToken = pickCookieValue(cookies, 'auth_token');
97 const ct0 = pickCookieValue(cookies, 'ct0');
98 if (authToken) {
99 out.authToken = authToken;
100 }
101 if (ct0) {
102 out.ct0 = ct0;
103 }
104 if (out.authToken && out.ct0) {
105 out.cookieHeader = cookieHeader(out.authToken, out.ct0);
106 out.source = labelForSource(options.source, options.source === 'chrome' ? options.chromeProfile : options.firefoxProfile);
107 return { cookies: out, warnings };
108 }
109 if (options.source === 'safari') {
110 warnings.push('No Twitter cookies found in Safari. Make sure you are logged into x.com in Safari.');
111 }
112 else if (options.source === 'chrome') {
113 warnings.push('No Twitter cookies found in Chrome. Make sure you are logged into x.com in Chrome.');
114 }
115 else {
116 warnings.push('No Twitter cookies found in Firefox. Make sure you are logged into x.com in Firefox and the profile exists.');
117 }
118 return { cookies: out, warnings };
119 }
120 async function extractCookiesFromBrowser(options) {
121 const { getCookies } = await loadSweetCookie();
122 return readTwitterCookiesFromBrowser({
123 getCookies,
124 ...options,
125 });
126 }
127 export async function extractCookiesFromSafari() {
128 return extractCookiesFromBrowser({ source: 'safari' });
129 }
130 export async function extractCookiesFromChrome(profile) {
131 return extractCookiesFromBrowser({ source: 'chrome', chromeProfile: profile });
132 }
133 export async function extractCookiesFromFirefox(profile) {
134 return extractCookiesFromBrowser({ source: 'firefox', firefoxProfile: profile });
135 }
136 /**
137 * Resolve Twitter credentials from multiple sources.
138 * Priority: CLI args > environment variables > browsers (ordered).
139 */
140 export async function resolveCredentials(options) {
141 const warnings = [];
142 const cookies = buildEmpty();
143 const disableBrowserCookies = envFlagEnabled('BIRD_DISABLE_BROWSER_COOKIES') ||
144 envFlagEnabled('LAST30DAYS_DISABLE_BROWSER_COOKIES');
145 const cookieTimeoutMs = typeof options.cookieTimeoutMs === 'number' &&
146 Number.isFinite(options.cookieTimeoutMs) &&
147 options.cookieTimeoutMs > 0
148 ? options.cookieTimeoutMs
149 : process.platform === 'darwin'
150 ? DEFAULT_COOKIE_TIMEOUT_MS
151 : undefined;
152 if (options.authToken) {
153 cookies.authToken = options.authToken;
154 cookies.source = 'CLI argument';
155 }
156 if (options.ct0) {
157 cookies.ct0 = options.ct0;
158 if (!cookies.source) {
159 cookies.source = 'CLI argument';
160 }
161 }
162 readEnvCookie(cookies, ['AUTH_TOKEN', 'TWITTER_AUTH_TOKEN'], 'authToken');
163 readEnvCookie(cookies, ['CT0', 'TWITTER_CT0'], 'ct0');
164 if (cookies.authToken && cookies.ct0) {
165 cookies.cookieHeader = cookieHeader(cookies.authToken, cookies.ct0);
166 return { cookies, warnings };
167 }
168 if (disableBrowserCookies) {
169 if (!cookies.authToken) {
170 warnings.push('Missing auth_token - provide via --auth-token, AUTH_TOKEN env var, or disable BIRD_DISABLE_BROWSER_COOKIES to allow browser cookie lookup');
171 }
172 if (!cookies.ct0) {
173 warnings.push('Missing ct0 - provide via --ct0, CT0 env var, or disable BIRD_DISABLE_BROWSER_COOKIES to allow browser cookie lookup');
174 }
175 return { cookies, warnings };
176 }
177 const sourcesToTry = resolveSources(options.cookieSource);
178 let getCookies;
179 try {
180 ({ getCookies } = await loadSweetCookie());
181 }
182 catch (error) {
183 if (error?.code !== 'ERR_MODULE_NOT_FOUND' ||
184 !String(error.message ?? '').includes('@steipete/sweet-cookie')) {
185 throw error;
186 }
187 warnings.push('Browser cookie lookup unavailable because vendored dependency @steipete/sweet-cookie is not installed.');
188 if (!cookies.authToken) {
189 warnings.push('Missing auth_token - provide via --auth-token, AUTH_TOKEN env var, or login to x.com in Safari/Chrome/Firefox');
190 }
191 if (!cookies.ct0) {
192 warnings.push('Missing ct0 - provide via --ct0, CT0 env var, or login to x.com in Safari/Chrome/Firefox');
193 }
194 return { cookies, warnings };
195 }
196 for (const source of sourcesToTry) {
197 const res = await readTwitterCookiesFromBrowser({
198 getCookies,
199 source,
200 chromeProfile: options.chromeProfile,
201 firefoxProfile: options.firefoxProfile,
202 cookieTimeoutMs,
203 });
204 warnings.push(...res.warnings);
205 if (res.cookies.authToken && res.cookies.ct0) {
206 return { cookies: res.cookies, warnings };
207 }
208 }
209 if (!cookies.authToken) {
210 warnings.push('Missing auth_token - provide via --auth-token, AUTH_TOKEN env var, or login to x.com in Safari/Chrome/Firefox');
211 }
212 if (!cookies.ct0) {
213 warnings.push('Missing ct0 - provide via --ct0, CT0 env var, or login to x.com in Safari/Chrome/Firefox');
214 }
215 if (cookies.authToken && cookies.ct0) {
216 cookies.cookieHeader = cookieHeader(cookies.authToken, cookies.ct0);
217 }
218 return { cookies, warnings };
219 }
220 //# sourceMappingURL=cookies.js.map
221
221 lines JAVASCRIPT