# Deno Deploy 代理 — 绕过微信机房 IP 封锁

## 问题

腾讯云/阿里云等 IDC 机房的 IP 被微信公众平台识别并限制，即使 we-mp-rss 登录成功、浏览器正确，文章列表 API 也返回空结果（"成功0条"）。

**⚠️ 2026-08: Deno Deploy signups currently unavailable (403 SIGNUP_UNAVAILABLE).** If you can't create a new Deno account, see [`references/china-proxy-platforms.md`](references/china-proxy-platforms.md) for alternatives (Cloudflare Pages, Vercel, Netlify). Also note: `*.deno.dev` domain accessibility from China is unverified at this time.

## 解决方案

部署一个 Deno Deploy 边缘代理，转发 we-mp-rss 的微信请求。Deno 的边缘节点 IP 不被识别为机房 IP。

## 部署步骤（用户操作 — 两种方式）

### 方式一：Web Dashboard（首选）

1. 打开 `https://dash.deno.com`
2. **用 Google 账号登录**（不需要 GitHub！Google 登录入口在页面下方）
3. 点 New Playground
4. 粘贴 `https://rss.gdcjgk.net/static/wechat-proxy.js` 中的代码
5. 点 Save & Deploy
6. 复制得到的 URL（如 `https://xxx.deno.dev`）

**⚠️ 中国大陆访问问题：** `dash.deno.com` 在国内可能加载不出来。如果打不开，试：
- 切到手机流量（4G/5G）
- 换非办公网络（家庭宽带）
- 或改用下面的 CLI 方式

### 方式二：CLI 部署（从服务器操作）

Deno CLI 已安装在服务器上（`/home/ubuntu/.deno/bin/deno`），服务器可以访问 deno.com。

```bash
# 1. 确保 deployctl 可用
deno install -g --force -n deployctl jsr:@deno/deployctl

# 2. 部署（需要 Deno access token — 获取方式见下）
deployctl deploy --token=<DENO_ACCESS_TOKEN> --project=wechat-proxy /tmp/wechat_proxy.ts
```

**获取 Access Token（需用户操作一次）：**
- 能访问 dash.deno.com → Account Settings → Access Tokens → Create
- 如果完全打不开 deno.com → CLI 方式不可行，回到方式一

**代理代码（适用于 Deno 2.x `Deno.serve` API）：**
```ts
Deno.serve(async (req: Request) => {
  const url = new URL(req.url);
  const targetUrl = url.searchParams.get("url");
  if (!targetUrl) return new Response("Missing ?url=", { status: 400 });
  if (!targetUrl.includes("mp.weixin.qq.com")) return new Response("blocked", { status: 403 });
  
  const resp = await fetch(targetUrl, {
    headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }
  });
  const body = await resp.text();
  return new Response(body, {
    status: resp.status,
    headers: { "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*" }
  });
});
```

## 服务端配置

拿到 Deno URL 后，有两处需要修改：

### 1. 配置代理（config.yaml / systemd env）

```yaml
proxy:
  enabled: True
  deno_url: https://xxx.deno.dev
```

通过 systemd 环境变量设置（推荐，无需改文件）：
```
Environment="PROXY_ENABLED=True"
Environment="PROXY_DENO_URL=https://xxx.deno.dev"
```

### 2. 修补 api.py — 文章列表抓取走代理（必须！）

**当前代码的代理只覆盖了文章内容提取（`content_extract()`），文章列表抓取（`api.py` 第 55 行）仍然是直连！** 只配 config 不修代码 = 代理不生效。
# 边缘代理 — 绕过微信机房 IP 封锁

## 问题

腾讯云/阿里云等 IDC 机房的 IP 被微信公众平台识别并限制，即使 we-mp-rss 登录成功、浏览器正确，文章列表 API 也返回空结果（"成功0条"，日志中 `200013 freq control`）。

**⚠️ 冷却方案无效**：已验证暂停 15 小时 + 降低频率仍返回 200013。机房 IP 被永久标记后，降温无用，必须上代理。

## 平台选择

| 平台 | 状态 | 费用 |
|------|------|------|
| **Cloudflare Workers**（推荐） | 可用（已有 CF 账号） | 免费 10 万次/天 |
| Deno Deploy | ❌ 注册已关闭（SIGNUP_UNAVAILABLE） | — |

## 方案 A：Cloudflare Workers（推荐）

### 部署步骤（用户操作）

1. 打开 [Cloudflare Dashboard](https://dash.cloudflare.com) → Workers & Pages → Create
2. 粘贴下方代理代码 → Deploy
3. 复制得到的 URL（如 `https://wechat-proxy.xxx.workers.dev`）

### 代理代码（Deno / CF Workers 通用）

```js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    const target = url.searchParams.get("url");
    if (!target) return new Response("OK");
    if (!target.includes("mp.weixin.qq.com")) return new Response("blocked", { status: 403 });

    const headers = new Headers(request.headers);
    headers.set("Host", new URL(target).host);
    headers.delete("origin");
    headers.delete("referer");

    try {
      const resp = await fetch(target, {
        method: request.method,
        headers,
        body: request.method === "POST" ? await request.text() : undefined,
      });
      const out = new Headers(resp.headers);
      out.delete("transfer-encoding");
      out.delete("content-encoding");
      return new Response(resp.body, { status: resp.status, headers: out });
    } catch {
      return new Response("proxy error", { status: 502 });
    }
  }
};
```

代理代码也托管在 `https://rss.gdcjgk.net/static/wechat-proxy.js`，可直接复制。

### 安全措施

代理代码中已添加域名白名单：只允许访问 `mp.weixin.qq.com`，其他域名返回 403。

## 方案 B：Deno Deploy（可用！通过 CLI 授权）

**Deno Deploy 仍然可用，不需要注册新账号。** 服务器可以通过 `deployctl` CLI 发起部署，生成一个短授权链接让用户在手机上打开即可。

### CLI 授权部署流程（2026-08-05 验证可行）

**前提：** 服务器已安装 Deno（`/home/ubuntu/.deno/bin/deno`），可以正常访问 `deno.com`。

**步骤 1 — 服务器准备代理代码：**

```bash
mkdir -p /tmp/deno-deploy
cat > /tmp/deno-deploy/main.ts << 'EOF'
Deno.serve(async (req: Request) => {
  const url = new URL(req.url);
  const targetUrl = url.searchParams.get("url");
  if (!targetUrl) return new Response("Missing ?url=", { status: 400 });
  if (!targetUrl.includes("mp.weixin.qq.com")) return new Response("blocked", { status: 403 });
  try {
    const resp = await fetch(targetUrl, {
      headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" }
    });
    const body = await resp.text();
    return new Response(body, {
      status: resp.status,
      headers: { "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*" }
    });
  } catch (e) {
    return new Response(JSON.stringify({ error: e.message }), { status: 502 });
  }
});
EOF
```

**步骤 2 — 服务器发起部署（生成授权链接）：**

```bash
# deployctl 需要权限标志安装
deno install -g --force --allow-sys --allow-env --allow-net --allow-read --allow-write \
  -n deployctl jsr:@deno/deployctl

# 发起部署（会打印授权 URL 然后等待）
export PATH="/home/ubuntu/.deno/bin:$PATH"
cd /tmp/deno-deploy && deployctl deploy --project=gdcjgk-proxy --entrypoint=main.ts
```

输出示例：
```
Authorization URL: https://dash.deno.com/signin/cli?claim_challenge=IPYGrX4UewTBGVgocMNCHC0zeYIP9_NMV1ePMhhI5k8
⚠ Cannot open the authorization URL automatically. Please navigate to it manually.
Waiting for authorization...
```

**步骤 3 — 用户用手机打开授权链接：**

把 `https://dash.deno.com/signin/cli?claim_challenge=...` 发给用户。这是 CLI 专用授权链接，**不是** `dash.deno.com` 主页，更短更轻量。用户在手机上（4G/5G 流量）打开 → 点授权 → 服务器端自动继续部署。

**步骤 4 — 服务器收到授权后自动完成：**

```
✅ Successfully deployed
Project URL: https://gdcjgk-proxy-xxxxxxxx.deno.dev
```

### 注意事项

- **`deployctl` 权限安装：** 首次安装必须加 `--allow-sys --allow-env --allow-net --allow-read --allow-write` 全部五个标志。缺少 `--allow-sys` 会报 `NotCapable: Requires sys access to "osRelease"`。
- **PATH 要求：** 运行 `deployctl` 前必须 `export PATH="/home/ubuntu/.deno/bin:$PATH"`，否则报 `deno: not found`。
- **授权链接有效期约 15 分钟**，超时需重新发起。用户打开后需立即点授权。
- **授权链接也可能在中国不可访问：** CLAIM_CHALLENGE 链接的域名仍是 `dash.deno.com`（与主页相同）。如果用户在手机 4G 流量下也打不开，说明该域名在国内被墙。此时放弃 Deno，改用方案 A（用户本机跑代理脚本）。
- 服务器上的 `deployctl` 进程在等待授权期间会一直运行，不要中断。
- 代理代码也托管在 `https://rss.gdcjgk.net/static/wechat-proxy.js`（Deno/CF Workers 兼容格式）

## 服务端配置（拿到代理 URL 后）

### 1. 设置环境变量

在 systemd unit 中添加（或通过 `sudo systemctl edit we-mp-rss`）：

```
Environment=PROXY_ENABLED=True
Environment=PROXY_DENO_URL=https://xxx.workers.dev
```

### 2. ⚠️ 必须修补代码 — 文章列表抓取不走代理

**当前代码的代理支持只覆盖了文章内容提取（`content_extract()`），不覆盖文章列表抓取（`api.py` / `web.py` 的 `get_Articles()`）。即使配了 `PROXY_DENO_URL`，列表抓取仍是直连，会继续被 200013 拦截。**

#### 补丁 1：`core/wx/model/api.py`（api 模式）

在文件头部添加 `import urllib.parse` 和 `from core.print import print_info`：

```python
import urllib.parse
from core.print import print_error, print_info
```

将第 55 行的直连请求替换为代理感知版本：

```python
# 替换前：
resp = session.get(url, headers=headers, params = params, verify=False)

# 替换后：
if self.proxy_enabled and self.deno_proxy_url:
    full_url = url + "?" + urllib.parse.urlencode(params)
    proxy_url = f"{self.deno_proxy_url}?url={urllib.parse.quote(full_url, safe='')}"
    print_info(f"使用代理请求: {proxy_url[:100]}...")
    resp = session.get(proxy_url, headers=headers, verify=False)
else:
    resp = session.get(url, headers=headers, params = params, verify=False)
```

#### 补丁 2：`core/wx/model/web.py`（web 模式）

同上，将第 59 行的直连请求替换：

```python
# 替换后（web.py 的 timeout 参数保留）：
if self.proxy_enabled and self.deno_proxy_url:
    full_url = url + "?" + urllib.parse.urlencode(params)
    proxy_url = f"{self.deno_proxy_url}?url={urllib.parse.quote(full_url, safe='')}"
    print_info(f"使用代理请求: {proxy_url[:100]}...")
    resp = session.get(proxy_url, headers=headers, verify=False, timeout=(10, 30))
else:
    resp = session.get(url, headers=headers, params = params, verify=False, timeout=(10, 30))
```

### 3. 切换到正确的采集端点

`config.yaml` 中 `gather.model` 默认值为 `web`，它使用 `/cgi-bin/appmsgpublish` 端点（返回草稿箱，对已群发文章始终为空）。必须切到 `api` 模式：

```yaml
gather:
  model: ${GATHER.MODEL:-api}  # 从 web 改为 api
```

### 4. 重启服务 + 验证

```bash
sudo systemctl restart we-mp-rss
# 等 10 秒后手动触发测试
TOKEN=*** -s -X POST 'http://localhost:8001/api/v1/wx/auth/login' -H 'Content-Type: application/x-www-form-urlencoded' -d 'username=gdcjgk&password=admin888' | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['access_token'])")
curl -s -H "Authorization: Bearer ***'http://localhost:8001/api/v1/wx/mps/update/MP_WXS_3886864470'
# 等 10 秒后查日志：不应再出现 "frequencey control"
journalctl -u we-mp-rss --no-pager --since "30 seconds ago" | grep -iE "freq|成功|代理"
```

## 费用

Cloudflare Workers 免费额度 10 万次请求/天。每天拉几十篇公众号文章绰绰有余。
