feat(auth): allow selecting cookie source
This commit is contained in:
+38
-83
@@ -12,11 +12,11 @@
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Command } from 'commander';
|
||||
import { Command, Option } from 'commander';
|
||||
import JSON5 from 'json5';
|
||||
import kleur from 'kleur';
|
||||
import { resolveCliInvocation } from './lib/cli-args.js';
|
||||
import { resolveCredentials } from './lib/cookies.js';
|
||||
import { type CookieSource, resolveCredentials } from './lib/cookies.js';
|
||||
import { extractTweetId } from './lib/extract-tweet-id.js';
|
||||
import { mentionsQueryFromUserOption, normalizeHandle } from './lib/normalize-handle.js';
|
||||
import {
|
||||
@@ -70,6 +70,7 @@ const colors = {
|
||||
type BirdConfig = {
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieSource?: CookieSource;
|
||||
allowSafari?: boolean;
|
||||
allowChrome?: boolean;
|
||||
allowFirefox?: boolean;
|
||||
@@ -145,6 +146,11 @@ program
|
||||
.option('--ct0 <token>', 'Twitter ct0 cookie')
|
||||
.option('--chrome-profile <name>', 'Chrome profile name for cookie extraction', config.chromeProfile)
|
||||
.option('--firefox-profile <name>', 'Firefox profile name for cookie extraction', config.firefoxProfile)
|
||||
.addOption(
|
||||
new Option('--cookie-source <source>', 'Cookie source for browser cookie extraction')
|
||||
.choices(['auto', 'safari', 'chrome', 'firefox'])
|
||||
.default(config.cookieSource ?? 'auto'),
|
||||
)
|
||||
.option('--media <path>', 'Attach media file (repeatable, up to 4 images or 1 video)', collect, [])
|
||||
.option('--alt <text>', 'Alt text for the corresponding --media (repeatable)', collect, [])
|
||||
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
||||
@@ -152,6 +158,27 @@ program
|
||||
.option('--no-emoji', 'Disable emoji output')
|
||||
.option('--no-color', 'Disable ANSI colors (or set NO_COLOR)');
|
||||
|
||||
type CredentialsOptions = {
|
||||
authToken?: string;
|
||||
ct0?: string;
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieSource?: CookieSource;
|
||||
};
|
||||
|
||||
function resolveCredentialsFromOptions(opts: CredentialsOptions) {
|
||||
return resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
cookieSource: opts.cookieSource ?? config.cookieSource ?? 'auto',
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
program.hook('preAction', (_thisCommand, actionCommand) => {
|
||||
applyOutputFromCommand(actionCommand);
|
||||
});
|
||||
@@ -318,15 +345,7 @@ program
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -385,15 +404,7 @@ program
|
||||
}
|
||||
const tweetId = extractTweetId(tweetIdOrUrl);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -448,15 +459,7 @@ program
|
||||
|
||||
const tweetId = extractTweetId(tweetIdOrUrl);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -498,15 +501,7 @@ program
|
||||
const timeoutMs = resolveTimeoutFromOptions(opts);
|
||||
const tweetId = extractTweetId(tweetIdOrUrl);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -539,15 +534,7 @@ program
|
||||
const timeoutMs = resolveTimeoutFromOptions(opts);
|
||||
const tweetId = extractTweetId(tweetIdOrUrl);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -581,15 +568,7 @@ program
|
||||
const timeoutMs = resolveTimeoutFromOptions(opts);
|
||||
const count = Number.parseInt(cmdOpts.count || '10', 10);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -631,15 +610,7 @@ program
|
||||
|
||||
let query: string | null = fromUserOpt.query;
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -683,15 +654,7 @@ program
|
||||
const opts = program.opts();
|
||||
const timeoutMs = resolveTimeoutFromOptions(opts);
|
||||
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
for (const warning of warnings) {
|
||||
console.error(`${p('warn')}${warning}`);
|
||||
@@ -728,15 +691,7 @@ program
|
||||
.description('Check credential availability')
|
||||
.action(async () => {
|
||||
const opts = program.opts();
|
||||
const { cookies, warnings } = await resolveCredentials({
|
||||
authToken: opts.authToken,
|
||||
ct0: opts.ct0,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
allowSafari: config.allowSafari ?? true,
|
||||
allowChrome: config.allowChrome ?? true,
|
||||
allowFirefox: config.allowFirefox ?? true,
|
||||
});
|
||||
const { cookies, warnings } = await resolveCredentialsFromOptions(opts);
|
||||
|
||||
console.log(`${p('info')}Credential check`);
|
||||
console.log('─'.repeat(40));
|
||||
|
||||
+63
-37
@@ -20,6 +20,8 @@ export interface CookieExtractionResult {
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export type CookieSource = 'auto' | 'safari' | 'chrome' | 'firefox';
|
||||
|
||||
function normalizeValue(value: unknown): string | null {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
@@ -444,6 +446,7 @@ export async function extractCookiesFromFirefox(profile?: string): Promise<Cooki
|
||||
export async function resolveCredentials(options: {
|
||||
authToken?: string;
|
||||
ct0?: string;
|
||||
cookieSource?: CookieSource;
|
||||
allowSafari?: boolean;
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
@@ -457,6 +460,8 @@ export async function resolveCredentials(options: {
|
||||
source: null,
|
||||
};
|
||||
|
||||
const cookieSource: CookieSource = options.cookieSource ?? 'auto';
|
||||
|
||||
// 1. CLI arguments (highest priority)
|
||||
if (options.authToken) {
|
||||
cookies.authToken = options.authToken;
|
||||
@@ -497,48 +502,69 @@ export async function resolveCredentials(options: {
|
||||
const allowChrome = options.allowChrome ?? true;
|
||||
const allowFirefox = options.allowFirefox ?? true;
|
||||
|
||||
// 3. Safari cookies (preferred browser fallback)
|
||||
if (allowSafari && (!cookies.authToken || !cookies.ct0)) {
|
||||
const safariResult = await extractCookiesFromSafari();
|
||||
warnings.push(...safariResult.warnings);
|
||||
const sourcesToTry: Array<Exclude<CookieSource, 'auto'>> =
|
||||
cookieSource === 'auto' ? ['safari', 'chrome', 'firefox'] : [cookieSource];
|
||||
|
||||
if (!cookies.authToken && safariResult.cookies.authToken) {
|
||||
cookies.authToken = safariResult.cookies.authToken;
|
||||
cookies.source = safariResult.cookies.source;
|
||||
}
|
||||
if (!cookies.ct0 && safariResult.cookies.ct0) {
|
||||
cookies.ct0 = safariResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = safariResult.cookies.source;
|
||||
}
|
||||
}
|
||||
for (const source of sourcesToTry) {
|
||||
if (cookies.authToken && cookies.ct0) break;
|
||||
|
||||
// 4. Chrome cookies (secondary browser fallback)
|
||||
if (allowChrome && (!cookies.authToken || !cookies.ct0)) {
|
||||
const chromeResult = await extractCookiesFromChrome(options.chromeProfile);
|
||||
warnings.push(...chromeResult.warnings);
|
||||
if (source === 'safari') {
|
||||
if (!allowSafari) {
|
||||
warnings.push('Safari cookie extraction disabled (allowSafari=false).');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cookies.authToken && chromeResult.cookies.authToken) {
|
||||
cookies.authToken = chromeResult.cookies.authToken;
|
||||
cookies.source = chromeResult.cookies.source;
|
||||
}
|
||||
if (!cookies.ct0 && chromeResult.cookies.ct0) {
|
||||
cookies.ct0 = chromeResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = chromeResult.cookies.source;
|
||||
}
|
||||
}
|
||||
const safariResult = await extractCookiesFromSafari();
|
||||
warnings.push(...safariResult.warnings);
|
||||
|
||||
// 5. Firefox cookies (tertiary browser fallback)
|
||||
if (allowFirefox && (!cookies.authToken || !cookies.ct0)) {
|
||||
const firefoxResult = await extractCookiesFromFirefox(options.firefoxProfile);
|
||||
warnings.push(...firefoxResult.warnings);
|
||||
|
||||
if (!cookies.authToken && firefoxResult.cookies.authToken) {
|
||||
cookies.authToken = firefoxResult.cookies.authToken;
|
||||
cookies.source = firefoxResult.cookies.source;
|
||||
if (!cookies.authToken && safariResult.cookies.authToken) {
|
||||
cookies.authToken = safariResult.cookies.authToken;
|
||||
cookies.source = safariResult.cookies.source;
|
||||
}
|
||||
if (!cookies.ct0 && safariResult.cookies.ct0) {
|
||||
cookies.ct0 = safariResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = safariResult.cookies.source;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!cookies.ct0 && firefoxResult.cookies.ct0) {
|
||||
cookies.ct0 = firefoxResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = firefoxResult.cookies.source;
|
||||
|
||||
if (source === 'chrome') {
|
||||
if (!allowChrome) {
|
||||
warnings.push('Chrome cookie extraction disabled (allowChrome=false).');
|
||||
continue;
|
||||
}
|
||||
|
||||
const chromeResult = await extractCookiesFromChrome(options.chromeProfile);
|
||||
warnings.push(...chromeResult.warnings);
|
||||
|
||||
if (!cookies.authToken && chromeResult.cookies.authToken) {
|
||||
cookies.authToken = chromeResult.cookies.authToken;
|
||||
cookies.source = chromeResult.cookies.source;
|
||||
}
|
||||
if (!cookies.ct0 && chromeResult.cookies.ct0) {
|
||||
cookies.ct0 = chromeResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = chromeResult.cookies.source;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source === 'firefox') {
|
||||
if (!allowFirefox) {
|
||||
warnings.push('Firefox cookie extraction disabled (allowFirefox=false).');
|
||||
continue;
|
||||
}
|
||||
|
||||
const firefoxResult = await extractCookiesFromFirefox(options.firefoxProfile);
|
||||
warnings.push(...firefoxResult.warnings);
|
||||
|
||||
if (!cookies.authToken && firefoxResult.cookies.authToken) {
|
||||
cookies.authToken = firefoxResult.cookies.authToken;
|
||||
cookies.source = firefoxResult.cookies.source;
|
||||
}
|
||||
if (!cookies.ct0 && firefoxResult.cookies.ct0) {
|
||||
cookies.ct0 = firefoxResult.cookies.ct0;
|
||||
if (!cookies.source) cookies.source = firefoxResult.cookies.source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user