Track bundled vendor runtime sources
Some checks failed
CI / main (push) Has been cancelled
CI / release-e2e (push) Has been cancelled

This commit is contained in:
2026-07-07 10:05:50 +08:00
parent fbe179ab53
commit 52636c91ae
2111 changed files with 1850012 additions and 14 deletions

View File

@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"""Reddit — multi-backend: OpenCLI / rdt-cli. Login is mandatory.
Honest tiering (live-verified 2026-06): there is NO zero-config path.
Anonymous .json endpoints are blocked (403 anti-bot, all variants), and
the official API closed self-service registration in 2025-11 (manual
approval, individual scripts rarely granted — PRAW is only an option for
users who already hold credentials). Every working backend rides a
logged-in session: OpenCLI reuses the browser's, rdt-cli imports cookies.
"""
import json
import shutil
import subprocess
from agent_reach.utils.process import utf8_subprocess_env
from .base import Channel
_CREDENTIAL_FILE = "~/.config/rdt-cli/credential.json"
# Pinned to the 0.4.2 state — PyPI still only has 0.4.1 (upstream issue #10).
_RDT_GIT_SOURCE = "git+https://github.com/public-clis/rdt-cli.git@5e4fb3720d5c174e976cd425ccc3b879d52cac66"
#: shell 对"找到但不可执行/找不到"使用的退出码(对齐 agent_reach.probe
_BROKEN_EXIT_CODES = (126, 127)
#: rdt 应从固定 git 源安装PyPI 落后),断链处方与 probe 默认的 pipx/uv 不同
_RDT_BROKEN_HINT = (
"rdt 命令存在但无法执行——通常是系统 Python 升级后 venv 解释器丢失。\n"
"PyPI 版本落后,推荐用固定 git 源强制重装:\n"
f" pipx install --force '{_RDT_GIT_SOURCE}'"
)
class RedditChannel(Channel):
name = "reddit"
description = "Reddit 帖子和评论"
backends = ["OpenCLI", "rdt-cli"]
tier = 1 # no zero-config path exists — see module docstring
def can_handle(self, url: str) -> bool:
from urllib.parse import urlparse
d = urlparse(url).netloc.lower()
return "reddit.com" in d or "redd.it" in d
def check(self, config=None):
"""Probe candidates in order; first fully-usable backend wins."""
self.active_backend = None
findings = []
for backend in self.ordered_backends(config):
if backend == "OpenCLI":
result = self._check_opencli()
else:
result = self._check_rdt()
if result is None:
continue
findings.append((backend, *result))
for wanted in ("ok", "warn"):
for backend, status, message in findings:
if status == wanted:
self.active_backend = backend
return status, message
if findings:
return "error", "\n".join(m for _, _, m in findings)
return "off", (
"未安装任何 Reddit 后端。注意Reddit 没有零配置路径"
"(匿名 .json 已被封,官方 API 需人工审批),必须用登录态。推荐:\n"
" 桌面agent-reach install --channels opencli\n"
" (复用 Chrome 登录态,登录过 reddit.com 即可用)\n"
f" 服务器/存量pipx install '{_RDT_GIT_SOURCE}'\n"
" 然后 `rdt login` 或手动写入 Cookie见 doctor 提示)\n"
"中国大陆访问 Reddit 需要代理"
)
def _check_opencli(self):
"""OpenCLI candidate. None = not installed."""
from agent_reach.backends import opencli_status
st = opencli_status()
if not st.installed:
return None
if st.broken:
return "error", st.hint
if st.ready:
return "ok", (
"OpenCLI 可用(复用浏览器登录态)。用法:"
"opencli reddit search/read/subreddit/hot -f yaml"
)
return "warn", st.hint
def _check_rdt(self):
"""rdt-cli candidate. None = not installed."""
rdt = shutil.which("rdt")
if not rdt:
return None
# 不走 probe_command实测 `rdt status --json` 成功时rc=0也会向 stderr
# 打网络重试日志probe 把 stdout+stderr 合并后 JSON 解析必炸。
# 故保留手写 subprocessstdout 单独捕获),但异常分类对齐 probe 语义:
# exec 失败/126/127 → brokenvenv 断链处方TimeoutExpired → 超时。
try:
r = subprocess.run(
[rdt, "status", "--json"],
capture_output=True,
encoding="utf-8",
errors="replace",
timeout=10,
env=utf8_subprocess_env(),
)
except subprocess.TimeoutExpired:
return "error", "rdt 响应超时(>10sReddit 状态未知。稍后重试或运行 `rdt status` 查看详情"
except OSError:
# 含 FileNotFoundErrorwhich 命中但 exec 失败 = venv 断链probe 的 broken
return "error", _RDT_BROKEN_HINT
if r.returncode in _BROKEN_EXIT_CODES:
return "error", _RDT_BROKEN_HINT
if r.returncode != 0:
detail = (r.stderr or r.stdout or "").strip().splitlines()
tail = detail[-1] if detail else "无输出"
return "error", f"rdt 异常退出exit {r.returncode}{tail}。运行 `rdt status` 查看详情"
# 进程正常退出 → rdt 本身是活的(无论登录与否)
try:
data = json.loads(r.stdout or "")
except json.JSONDecodeError:
data = None
if not isinstance(data, dict):
return "warn", "rdt-cli 可用但状态输出无法解析,运行 `rdt status` 查看登录状态"
info = data.get("data")
if not isinstance(info, dict):
info = {}
authenticated = info.get("authenticated", False)
username = info.get("username") or ""
if authenticated:
suffix = f"(已登录:{username}" if username else ""
return "ok", (
f"rdt-cli 可用{suffix}(搜索帖子、阅读全文、查看评论;"
"上游 2026-03 起停更,桌面用户建议迁移到 OpenCLI"
)
return "warn", (
"rdt-cli 已安装但未登录。Reddit 自 2024 年起要求认证,"
"未登录时所有请求均返回 403。\n\n"
"方法一(自动):运行 `rdt login`\n"
" 先在浏览器登录 reddit.com再运行此命令自动提取 Cookie。\n\n"
"方法二(手动,适用于 Chrome/Edge 127+ 无法自动提取时):\n"
" 1. Chrome 应用商店安装 Cookie-Editor 扩展:\n"
" https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm\n"
" 2. 在浏览器打开 reddit.com确保已登录\n"
" 3. 点击 Cookie-Editor 图标,找到 `reddit_session`,复制其 Value\n"
f" 4. 将以下内容写入 {_CREDENTIAL_FILE}\n"
' {"cookies": {"reddit_session": "<粘贴 Value>"}, '
'"source": "manual", "username": "<你的用户名>", '
'"modhash": null, "saved_at": 0, "last_verified_at": null}\n\n'
"验证:`rdt status --json` 确认 authenticated: true"
)