fix(xueqiu): add rookiepy as primary cookie backend, keep browser_cookie3 as fallback (#232)
browser_cookie3 has been unmaintained for 15+ months and breaks with newer browser versions. rookiepy (Rust-based, 345 stars, updated 2026-01) is now the preferred cookie extraction backend. - xueqiu.py: try rookiepy first, fallback to browser_cookie3 - cookie_extract.py: same priority order, compatible API wrapper - Remove bird CLI credential sync (bird repo deleted) - 104 tests passing Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,8 +79,16 @@ def _load_cookies_from_browser() -> bool:
|
||||
a local browser keep working.
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
import rookiepy
|
||||
cookies = rookiepy.chrome([".xueqiu.com"])
|
||||
if not any(c.get("name") == "xq_a_token" for c in cookies):
|
||||
return False
|
||||
for c in cookies:
|
||||
_cookie_jar.set(c["name"], c["value"], domain=c.get("domain", ".xueqiu.com"))
|
||||
return True
|
||||
except ImportError:
|
||||
import browser_cookie3
|
||||
|
||||
cookies = list(browser_cookie3.chrome(domain_name=".xueqiu.com"))
|
||||
if not any(c.name == "xq_a_token" for c in cookies):
|
||||
return False
|
||||
@@ -96,7 +104,7 @@ def _ensure_cookies() -> None:
|
||||
|
||||
Priority order:
|
||||
1. Saved cookie string in ~/.agent-reach/config.yaml (set by configure --from-browser)
|
||||
2. Live Chrome browser cookies via browser_cookie3 (if installed + logged in)
|
||||
2. Live Chrome browser cookies via rookiepy/browser_cookie3 (if installed + logged in)
|
||||
3. Homepage visit fallback (only yields anti-DDoS acw_tc,
|
||||
not enough for stock APIs)
|
||||
"""
|
||||
|
||||
@@ -52,14 +52,52 @@ def extract_all(browser: str = "chrome") -> Dict[str, dict]:
|
||||
"bilibili": {"SESSDATA": "xxx", "bili_jct": "yyy"},
|
||||
}
|
||||
"""
|
||||
# Try rookiepy first (Rust-based, more stable), fallback to browser_cookie3
|
||||
use_rookiepy = False
|
||||
try:
|
||||
import rookiepy
|
||||
use_rookiepy = True
|
||||
except ImportError:
|
||||
try:
|
||||
import browser_cookie3
|
||||
except ImportError:
|
||||
raise RuntimeError(
|
||||
"browser_cookie3 not installed. Run: pip install browser-cookie3"
|
||||
"Cookie extraction requires rookiepy or browser_cookie3.\n"
|
||||
"Install: pip install rookiepy (recommended)\n"
|
||||
" or: pip install browser-cookie3"
|
||||
)
|
||||
|
||||
# Get browser cookie jar
|
||||
browser = browser.lower()
|
||||
supported = ["chrome", "firefox", "edge", "brave", "opera"]
|
||||
if browser not in supported:
|
||||
raise ValueError(
|
||||
f"Unsupported browser: {browser}. Supported: {', '.join(supported)}"
|
||||
)
|
||||
|
||||
if use_rookiepy:
|
||||
# rookiepy returns list of dicts with name/value/domain/path keys
|
||||
try:
|
||||
browser_funcs = {
|
||||
"chrome": rookiepy.chrome,
|
||||
"firefox": rookiepy.firefox,
|
||||
"edge": rookiepy.edge,
|
||||
"brave": rookiepy.brave,
|
||||
"opera": rookiepy.opera,
|
||||
}
|
||||
raw_cookies = browser_funcs[browser]()
|
||||
# Wrap into objects with .name, .value, .domain for compatibility
|
||||
class _Cookie:
|
||||
def __init__(self, d):
|
||||
self.name = d.get("name", "")
|
||||
self.value = d.get("value", "")
|
||||
self.domain = d.get("domain", "")
|
||||
cookie_jar = [_Cookie(c) for c in raw_cookies]
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"Could not read {browser} cookies via rookiepy: {e}\n"
|
||||
f"Make sure {browser} is closed and you have permission."
|
||||
)
|
||||
else:
|
||||
browser_funcs = {
|
||||
"chrome": browser_cookie3.chrome,
|
||||
"firefox": browser_cookie3.firefox,
|
||||
@@ -67,20 +105,12 @@ def extract_all(browser: str = "chrome") -> Dict[str, dict]:
|
||||
"brave": browser_cookie3.brave,
|
||||
"opera": browser_cookie3.opera,
|
||||
}
|
||||
|
||||
browser = browser.lower()
|
||||
if browser not in browser_funcs:
|
||||
raise ValueError(
|
||||
f"Unsupported browser: {browser}. "
|
||||
f"Supported: {', '.join(browser_funcs.keys())}"
|
||||
)
|
||||
|
||||
try:
|
||||
cookie_jar = browser_funcs[browser]()
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f"Could not read {browser} cookies: {e}\n"
|
||||
f"Make sure {browser} is closed and you have permission to read its data."
|
||||
f"Make sure {browser} is closed and you have permission."
|
||||
)
|
||||
|
||||
results = {}
|
||||
@@ -193,8 +223,7 @@ def configure_from_browser(browser: str, config) -> List[Tuple[str, bool, str]]:
|
||||
if "auth_token" in tc and "ct0" in tc:
|
||||
config.set("twitter_auth_token", tc["auth_token"])
|
||||
config.set("twitter_ct0", tc["ct0"])
|
||||
# Sync credentials to bird CLI env and legacy xfetch session.json
|
||||
_sync_bird_env(tc["auth_token"], tc["ct0"])
|
||||
# Legacy sync (best-effort)
|
||||
_sync_xfetch_session(tc["auth_token"], tc["ct0"])
|
||||
results_list.append(("Twitter/X", True, "auth_token + ct0"))
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user