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:
Pnant
2026-03-31 15:54:59 +08:00
committed by GitHub
parent c5a304dc20
commit 21214fd02a
2 changed files with 71 additions and 34 deletions
+17 -9
View File
@@ -79,14 +79,22 @@ def _load_cookies_from_browser() -> bool:
a local browser keep working.
"""
try:
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
for c in cookies:
_cookie_jar.set_cookie(c)
return True
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
for c in cookies:
_cookie_jar.set_cookie(c)
return True
except Exception:
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)
"""
+54 -25
View File
@@ -52,36 +52,66 @@ 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 browser_cookie3
import rookiepy
use_rookiepy = True
except ImportError:
raise RuntimeError(
"browser_cookie3 not installed. Run: pip install browser-cookie3"
)
# Get browser cookie jar
browser_funcs = {
"chrome": browser_cookie3.chrome,
"firefox": browser_cookie3.firefox,
"edge": browser_cookie3.edge,
"brave": browser_cookie3.brave,
"opera": browser_cookie3.opera,
}
try:
import browser_cookie3
except ImportError:
raise RuntimeError(
"Cookie extraction requires rookiepy or browser_cookie3.\n"
"Install: pip install rookiepy (recommended)\n"
" or: pip install browser-cookie3"
)
browser = browser.lower()
if browser not in browser_funcs:
supported = ["chrome", "firefox", "edge", "brave", "opera"]
if browser not in supported:
raise ValueError(
f"Unsupported browser: {browser}. "
f"Supported: {', '.join(browser_funcs.keys())}"
f"Unsupported browser: {browser}. Supported: {', '.join(supported)}"
)
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."
)
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,
"edge": browser_cookie3.edge,
"brave": browser_cookie3.brave,
"opera": browser_cookie3.opera,
}
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."
)
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: