Make cookie-file auth env-only for multi-account use
All checks were successful
CI / test (3.11) (push) Successful in 55s
CI / test (3.10) (push) Successful in 1m58s
CI / test (3.12) (push) Successful in 1m0s

This commit is contained in:
2026-05-08 16:47:34 +02:00
parent bc82333e07
commit 4878d11b15
4 changed files with 8 additions and 18 deletions

View File

@@ -174,22 +174,17 @@ twitter follow elonmusk --json
twitter-cli uses this auth priority: twitter-cli uses this auth priority:
1. **Environment variables**: `TWITTER_AUTH_TOKEN` + `TWITTER_CT0` 1. **Environment variables**: `TWITTER_AUTH_TOKEN` + `TWITTER_CT0`
2. **Cookie file**: `TWITTER_COOKIE_FILE` or `config.yaml -> auth.cookieFile` 2. **Cookie file**: `TWITTER_COOKIE_FILE`
3. **Browser cookies** (recommended): auto-extract from Arc/Chrome/Edge/Firefox/Brave 3. **Browser cookies** (recommended): auto-extract from Arc/Chrome/Edge/Firefox/Brave
If you already exported a Netscape-format `cookies.txt`, point the CLI at it: If you already exported a Netscape-format `cookies.txt`, point the CLI at it:
```bash ```bash
export TWITTER_COOKIE_FILE=/path/to/cookies.txt TWITTER_COOKIE_FILE=/path/to/account-a.cookies.txt twitter whoami
twitter whoami TWITTER_COOKIE_FILE=/path/to/account-b.cookies.txt twitter whoami
``` ```
Or in `config.yaml`: This keeps the CLI multi-account friendly: no cookie path is pinned in config, so each command can target a different account cleanly.
```yaml
auth:
cookieFile: /path/to/cookies.txt
```
Browser extraction is recommended — it forwards ALL Twitter cookies (not just `auth_token` + `ct0`) and aligns request headers with your local runtime, which is closer to normal browser traffic than minimal cookie auth. Browser extraction is recommended — it forwards ALL Twitter cookies (not just `auth_token` + `ct0`) and aligns request headers with your local runtime, which is closer to normal browser traffic than minimal cookie auth.

View File

@@ -1,9 +1,6 @@
fetch: fetch:
count: 50 count: 50
auth:
cookieFile: /mnt/shared/cookies.txt
filter: filter:
mode: "topN" mode: "topN"
topN: 20 topN: 20

View File

@@ -103,6 +103,7 @@ def test_load_from_cookie_file_parses_netscape_cookie_dump(tmp_path) -> None:
def test_get_cookies_uses_cookie_file_before_browser(monkeypatch) -> None: def test_get_cookies_uses_cookie_file_before_browser(monkeypatch) -> None:
monkeypatch.setattr(auth, "load_from_env", lambda: None) monkeypatch.setattr(auth, "load_from_env", lambda: None)
monkeypatch.setenv("TWITTER_COOKIE_FILE", "/tmp/cookies.txt")
monkeypatch.setattr( monkeypatch.setattr(
auth, auth,
"load_from_cookie_file", "load_from_cookie_file",
@@ -116,7 +117,7 @@ def test_get_cookies_uses_cookie_file_before_browser(monkeypatch) -> None:
lambda auth_token, ct0, cookie_string=None: seen.append((auth_token, ct0, cookie_string)) or {}, lambda auth_token, ct0, cookie_string=None: seen.append((auth_token, ct0, cookie_string)) or {},
) )
cookies = auth.get_cookies({"auth": {"cookieFile": "/tmp/cookies.txt"}}) cookies = auth.get_cookies()
assert cookies["auth_token"] == "file-token" assert cookies["auth_token"] == "file-token"
assert seen == [("file-token", "file-csrf", "a=1")] assert seen == [("file-token", "file-csrf", "a=1")]

View File

@@ -2,7 +2,7 @@
Supports: Supports:
1. Environment variables: TWITTER_AUTH_TOKEN + TWITTER_CT0 1. Environment variables: TWITTER_AUTH_TOKEN + TWITTER_CT0
2. Cookie file: TWITTER_COOKIE_FILE or config auth.cookieFile (Netscape cookies.txt) 2. Cookie file: TWITTER_COOKIE_FILE (Netscape cookies.txt)
3. Auto-extract from browser via browser-cookie3 3. Auto-extract from browser via browser-cookie3
Extracts ALL Twitter cookies for full browser-like fingerprint. Extracts ALL Twitter cookies for full browser-like fingerprint.
Prefers in-process extraction (required on macOS for Keychain access), Prefers in-process extraction (required on macOS for Keychain access),
@@ -664,10 +664,7 @@ def get_cookies(config: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
# 2. Try cookie file from env/config # 2. Try cookie file from env/config
if not cookies: if not cookies:
auth_config = (config or {}).get("auth", {})
cookie_file = os.environ.get("TWITTER_COOKIE_FILE", "") cookie_file = os.environ.get("TWITTER_COOKIE_FILE", "")
if not cookie_file and isinstance(auth_config, dict):
cookie_file = str(auth_config.get("cookieFile", "") or "")
cookies = load_from_cookie_file(cookie_file) cookies = load_from_cookie_file(cookie_file)
if cookies: if cookies:
logger.info("Loaded cookies from cookie file %s", cookie_file) logger.info("Loaded cookies from cookie file %s", cookie_file)
@@ -687,7 +684,7 @@ def get_cookies(config: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
lines.extend(" " + line for line in hint.splitlines()) lines.extend(" " + line for line in hint.splitlines())
lines.append("") lines.append("")
lines.append("Option 1: Set TWITTER_AUTH_TOKEN and TWITTER_CT0 environment variables") lines.append("Option 1: Set TWITTER_AUTH_TOKEN and TWITTER_CT0 environment variables")
lines.append("Option 2: Set TWITTER_COOKIE_FILE or config auth.cookieFile to a Netscape cookies.txt export") lines.append("Option 2: Set TWITTER_COOKIE_FILE to a Netscape cookies.txt export")
lines.append("Option 3: Make sure you are logged into x.com in your browser (Arc/Chrome/Edge/Firefox/Brave)") lines.append("Option 3: Make sure you are logged into x.com in your browser (Arc/Chrome/Edge/Firefox/Brave)")
lines.append("") lines.append("")
lines.append("Run 'twitter -v <command>' for debug diagnostics.") lines.append("Run 'twitter -v <command>' for debug diagnostics.")