126 lines
3.7 KiB
Python
126 lines
3.7 KiB
Python
"""Cookie authentication for Twitter/X.
|
|
|
|
Supports:
|
|
1. Environment variables: TWITTER_AUTH_TOKEN + TWITTER_CT0
|
|
2. Auto-extract from browser via browser-cookie3 (subprocess)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from typing import Dict, Optional
|
|
|
|
|
|
def load_from_env() -> Optional[Dict[str, str]]:
|
|
"""Load cookies from environment variables."""
|
|
auth_token = os.environ.get("TWITTER_AUTH_TOKEN", "")
|
|
ct0 = os.environ.get("TWITTER_CT0", "")
|
|
if auth_token and ct0:
|
|
return {"auth_token": auth_token, "ct0": ct0}
|
|
return None
|
|
|
|
|
|
def extract_from_browser(browser: str = "chrome") -> Optional[Dict[str, str]]:
|
|
"""Auto-extract cookies from local browser using browser-cookie3.
|
|
|
|
Runs in a subprocess to avoid SQLite database lock issues when the
|
|
browser is running.
|
|
"""
|
|
extract_script = '''
|
|
import json, sys
|
|
try:
|
|
import browser_cookie3
|
|
except ImportError:
|
|
print(json.dumps({"error": "browser-cookie3 not installed"}))
|
|
sys.exit(1)
|
|
|
|
browser_funcs = {
|
|
"chrome": browser_cookie3.chrome,
|
|
"firefox": browser_cookie3.firefox,
|
|
"edge": browser_cookie3.edge,
|
|
"brave": browser_cookie3.brave,
|
|
}
|
|
|
|
browser_name = "%s"
|
|
fn = browser_funcs.get(browser_name)
|
|
if not fn:
|
|
print(json.dumps({"error": "Unsupported browser: " + browser_name}))
|
|
sys.exit(1)
|
|
|
|
try:
|
|
jar = fn()
|
|
except Exception as e:
|
|
print(json.dumps({"error": str(e)}))
|
|
sys.exit(1)
|
|
|
|
result = {}
|
|
for cookie in jar:
|
|
domain = cookie.domain or ""
|
|
if domain.endswith(".x.com") or domain.endswith(".twitter.com") or domain in ("x.com", "twitter.com", ".x.com", ".twitter.com"):
|
|
if cookie.name == "auth_token":
|
|
result["auth_token"] = cookie.value
|
|
elif cookie.name == "ct0":
|
|
result["ct0"] = cookie.value
|
|
|
|
if "auth_token" in result and "ct0" in result:
|
|
print(json.dumps(result))
|
|
else:
|
|
print(json.dumps({"error": "Could not find auth_token and ct0 cookies. Make sure you are logged into x.com in " + browser_name + "."}))
|
|
sys.exit(1)
|
|
''' % browser
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", extract_script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=15,
|
|
)
|
|
output = result.stdout.strip()
|
|
if not output:
|
|
stderr = result.stderr.strip()
|
|
if stderr:
|
|
# Maybe browser-cookie3 not installed, try with uv
|
|
result2 = subprocess.run(
|
|
["uv", "run", "--with", "browser-cookie3", "python3", "-c", extract_script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
)
|
|
output = result2.stdout.strip()
|
|
if not output:
|
|
return None
|
|
|
|
data = json.loads(output)
|
|
if "error" in data:
|
|
return None
|
|
return {"auth_token": data["auth_token"], "ct0": data["ct0"]}
|
|
except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError, FileNotFoundError):
|
|
return None
|
|
|
|
|
|
def get_cookies(browser: str = "chrome") -> Dict[str, str]:
|
|
"""Get Twitter cookies. Priority: env vars -> browser extraction.
|
|
|
|
Returns dict with 'auth_token' and 'ct0' keys.
|
|
Raises RuntimeError if no cookies found.
|
|
"""
|
|
# 1. Try environment variables
|
|
env_cookies = load_from_env()
|
|
if env_cookies:
|
|
return env_cookies
|
|
|
|
# 2. Try browser extraction
|
|
browser_cookies = extract_from_browser(browser)
|
|
if browser_cookies:
|
|
return browser_cookies
|
|
|
|
raise RuntimeError(
|
|
"No Twitter cookies found.\n"
|
|
"Option 1: Set TWITTER_AUTH_TOKEN and TWITTER_CT0 environment variables\n"
|
|
"Option 2: Make sure you are logged into x.com in your browser"
|
|
)
|