Compare commits
4 Commits
main
...
1ee30ecf28
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ee30ecf28 | |||
| 61d6536754 | |||
| 27b2cd2d02 | |||
| 7eae32d0b7 |
+35
-16
@@ -1143,19 +1143,19 @@ def _parse_twitter_cookie_input(value: str):
|
|||||||
|
|
||||||
|
|
||||||
def _configure_xhs_cookies(value):
|
def _configure_xhs_cookies(value):
|
||||||
"""Import cookies into xiaohongshu-mcp Docker container.
|
"""Import cookies for xhs-cli, with legacy xiaohongshu-mcp support.
|
||||||
|
|
||||||
Accepts two formats:
|
Accepts two formats:
|
||||||
1. Cookie-Editor JSON export (array of cookie objects)
|
1. Cookie-Editor JSON export (array of cookie objects)
|
||||||
2. Header String: "name1=value1; name2=value2; ..."
|
2. Header String: "name1=value1; name2=value2; ..."
|
||||||
|
|
||||||
The xiaohongshu-mcp container stores cookies at $COOKIES_PATH
|
xhs-cli stores cookies as a name/value dict at ~/.xiaohongshu-cli/cookies.json.
|
||||||
(default: /app/data/cookies.json or cookies.json in workdir).
|
The legacy Docker MCP stores Cookie-Editor style arrays at $COOKIES_PATH.
|
||||||
Format: JSON array of {name, value, domain, path, expires, httpOnly, secure, sameSite}.
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if not value:
|
if not value:
|
||||||
@@ -1165,6 +1165,7 @@ def _configure_xhs_cookies(value):
|
|||||||
|
|
||||||
# Detect format and parse
|
# Detect format and parse
|
||||||
cookies_json = None
|
cookies_json = None
|
||||||
|
cookies = []
|
||||||
|
|
||||||
# Try JSON format first (Cookie-Editor JSON export)
|
# Try JSON format first (Cookie-Editor JSON export)
|
||||||
if value.startswith("["):
|
if value.startswith("["):
|
||||||
@@ -1174,6 +1175,7 @@ def _configure_xhs_cookies(value):
|
|||||||
# Validate it looks like cookie objects
|
# Validate it looks like cookie objects
|
||||||
first = parsed[0]
|
first = parsed[0]
|
||||||
if isinstance(first, dict) and "name" in first and "value" in first:
|
if isinstance(first, dict) and "name" in first and "value" in first:
|
||||||
|
cookies = parsed
|
||||||
cookies_json = json.dumps(parsed)
|
cookies_json = json.dumps(parsed)
|
||||||
print(f" Parsed {len(parsed)} cookies from JSON format")
|
print(f" Parsed {len(parsed)} cookies from JSON format")
|
||||||
else:
|
else:
|
||||||
@@ -1188,7 +1190,6 @@ def _configure_xhs_cookies(value):
|
|||||||
|
|
||||||
# Header String format: "key1=val1; key2=val2; ..."
|
# Header String format: "key1=val1; key2=val2; ..."
|
||||||
if cookies_json is None and "=" in value:
|
if cookies_json is None and "=" in value:
|
||||||
cookies = []
|
|
||||||
for part in value.split(";"):
|
for part in value.split(";"):
|
||||||
part = part.strip()
|
part = part.strip()
|
||||||
if "=" not in part:
|
if "=" not in part:
|
||||||
@@ -1222,17 +1223,37 @@ def _configure_xhs_cookies(value):
|
|||||||
print(' 2. Header String: "key1=val1; key2=val2; ..."')
|
print(' 2. Header String: "key1=val1; key2=val2; ..."')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Primary path: configure xhs-cli directly.
|
||||||
|
xhs_cookie_map = {
|
||||||
|
str(c.get("name", "")).strip(): str(c.get("value", ""))
|
||||||
|
for c in cookies
|
||||||
|
if isinstance(c, dict) and str(c.get("name", "")).strip()
|
||||||
|
}
|
||||||
|
if xhs_cookie_map.get("a1"):
|
||||||
|
xhs_config_dir = os.path.expanduser("~/.xiaohongshu-cli")
|
||||||
|
os.makedirs(xhs_config_dir, exist_ok=True)
|
||||||
|
xhs_cookie_path = os.path.join(xhs_config_dir, "cookies.json")
|
||||||
|
with open(xhs_cookie_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump({**xhs_cookie_map, "saved_at": time.time()}, f, indent=2)
|
||||||
|
os.chmod(xhs_cookie_path, 0o600)
|
||||||
|
print(f"✅ xhs-cli cookies saved to {xhs_cookie_path}")
|
||||||
|
print(" Run `xhs status` or `agent-reach doctor` to verify.")
|
||||||
|
else:
|
||||||
|
print("[!] Cookie input does not include the required `a1` cookie.")
|
||||||
|
print(" xhs-cli will not treat this as a logged-in session. Export all xiaohongshu.com cookies from Cookie-Editor.")
|
||||||
|
|
||||||
|
# Keep a legacy Cookie-Editor array for users still running xiaohongshu-mcp.
|
||||||
|
legacy_cookie_path = os.path.expanduser("~/.agent-reach/xhs-cookies.json")
|
||||||
|
os.makedirs(os.path.dirname(legacy_cookie_path), exist_ok=True)
|
||||||
|
with open(legacy_cookie_path, "w") as f:
|
||||||
|
f.write(cookies_json)
|
||||||
|
os.chmod(legacy_cookie_path, 0o600)
|
||||||
|
print(f" Legacy MCP cookie array saved to {legacy_cookie_path}")
|
||||||
|
|
||||||
# Find the container
|
# Find the container
|
||||||
docker = shutil.which("docker")
|
docker = shutil.which("docker")
|
||||||
if not docker:
|
if not docker:
|
||||||
# No Docker - write to a local file for manual import
|
print(" Docker not found; skipping xiaohongshu-mcp import.")
|
||||||
cookie_path = os.path.expanduser("~/.agent-reach/xhs-cookies.json")
|
|
||||||
with open(cookie_path, "w") as f:
|
|
||||||
f.write(cookies_json)
|
|
||||||
os.chmod(cookie_path, 0o600)
|
|
||||||
print(f" Cookies saved to {cookie_path}")
|
|
||||||
print(" Docker not found. Copy manually:")
|
|
||||||
print(f" docker cp {cookie_path} xiaohongshu-mcp:/app/data/cookies.json")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if xiaohongshu-mcp container is running
|
# Check if xiaohongshu-mcp container is running
|
||||||
@@ -1243,9 +1264,7 @@ def _configure_xhs_cookies(value):
|
|||||||
)
|
)
|
||||||
container_name = result.stdout.strip()
|
container_name = result.stdout.strip()
|
||||||
if not container_name:
|
if not container_name:
|
||||||
print("[X] xiaohongshu-mcp container is not running.")
|
print(" xiaohongshu-mcp container is not running; skipping legacy Docker import.")
|
||||||
print(" Start it first:")
|
|
||||||
print(" docker run -d --name xiaohongshu-mcp -p 18060:18060 xpzouying/xiaohongshu-mcp")
|
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[X] Could not check Docker: {e}")
|
print(f"[X] Could not check Docker: {e}")
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ agent-reach doctor
|
|||||||
> 3. 点击 Cookie-Editor 图标 → Export → Header String
|
> 3. 点击 Cookie-Editor 图标 → Export → Header String
|
||||||
> 4. 把导出的字符串发给 Agent,运行:`agent-reach configure xhs-cookies "导出的cookie字符串"`
|
> 4. 把导出的字符串发给 Agent,运行:`agent-reach configure xhs-cookies "导出的cookie字符串"`
|
||||||
>
|
>
|
||||||
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。
|
> **注意**:不要依赖 QR 扫码登录,Cookie-Editor 导出方式最简单可靠。这个方式也适合 WSL、SSH、容器等无法直接读取桌面浏览器 Cookie 的环境。
|
||||||
|
|
||||||
|
`agent-reach configure xhs-cookies` 会把 Cookie 同步到 `~/.xiaohongshu-cli/cookies.json`,供 `xhs status/search/read` 直接使用;如果你还在用旧的 `xiaohongshu-mcp` Docker 方案,也会保留兼容导入文件。
|
||||||
|
|
||||||
## 使用示例
|
## 使用示例
|
||||||
|
|
||||||
@@ -69,6 +71,9 @@ A: 推荐使用住宅代理:`export HTTP_PROXY="http://user:pass@ip:port"`。
|
|||||||
**Q: xhs-cli 不支持我的系统?**
|
**Q: xhs-cli 不支持我的系统?**
|
||||||
A: 确保 Python 3.10+ 和 pipx 已安装。运行 `pipx install xiaohongshu-cli` 即可。
|
A: 确保 Python 3.10+ 和 pipx 已安装。运行 `pipx install xiaohongshu-cli` 即可。
|
||||||
|
|
||||||
|
**Q: WSL 里 `xhs login` 读不到 Windows 浏览器怎么办?**
|
||||||
|
A: 这是常见限制。WSL 里没有直接可读的 Linux 浏览器 Cookie 时,`xhs login` 自动提取会失败。推荐在 Windows 浏览器用 Cookie-Editor 导出 `xiaohongshu.com` 的 Header String,然后在 WSL 里运行 `agent-reach configure xhs-cookies "..."`,再用 `xhs status` 验证。
|
||||||
|
|
||||||
## 备选方案:Docker MCP
|
## 备选方案:Docker MCP
|
||||||
|
|
||||||
如果你已经在使用 [xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) Docker 方案,它也能正常工作:
|
如果你已经在使用 [xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) Docker 方案,它也能正常工作:
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Tests for Agent Reach CLI."""
|
"""Tests for Agent Reach CLI."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
@@ -42,6 +45,24 @@ class TestCLI:
|
|||||||
assert auth_token == "token123"
|
assert auth_token == "token123"
|
||||||
assert ct0 == "ct0abc"
|
assert ct0 == "ct0abc"
|
||||||
|
|
||||||
|
def test_configure_xhs_cookies_writes_xhs_cli_cookie_file(self, tmp_path, monkeypatch, capsys):
|
||||||
|
monkeypatch.setenv("HOME", str(tmp_path))
|
||||||
|
monkeypatch.setattr("shutil.which", lambda _name: None)
|
||||||
|
|
||||||
|
cli._configure_xhs_cookies("a1=token123; web_session=session456; other=value")
|
||||||
|
|
||||||
|
cookie_path = tmp_path / ".xiaohongshu-cli" / "cookies.json"
|
||||||
|
data = json.loads(cookie_path.read_text())
|
||||||
|
assert data["a1"] == "token123"
|
||||||
|
assert data["web_session"] == "session456"
|
||||||
|
assert "saved_at" in data
|
||||||
|
assert oct(os.stat(cookie_path).st_mode & 0o777) == "0o600"
|
||||||
|
|
||||||
|
legacy_path = tmp_path / ".agent-reach" / "xhs-cookies.json"
|
||||||
|
assert legacy_path.exists()
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "xhs-cli cookies saved" in captured.out
|
||||||
|
|
||||||
|
|
||||||
class TestCheckUpdateRetry:
|
class TestCheckUpdateRetry:
|
||||||
def test_retry_timeout_classification(self):
|
def test_retry_timeout_classification(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user