# China-Accessible Proxy Platforms for WeChat MP

Tested 2026-08-05 from 腾讯云 Lighthouse 北京 (120.53.228.229).

**Verified connectivity (from this server):**
- `vercel.com` → 200 ✅
- `netlify.app` → 301 ✅
- `pages.dev` → 404 (resolved, reachable) ✅
- `railway.app` → 301 ✅
- `workers.dev` → TIMEOUT ❌ (GFW DNS + IP block)
- `fly.dev` → TIMEOUT ❌
- `glitch.me` → TIMEOUT ❌
- All tested CF anycast IPs (104.16-26.x.x, 172.67.x.x) → TIMEOUT ❌

## Connectivity Matrix

| Platform | Domain | China Inbound | China Outbound | Deployment | Notes |
|----------|--------|---------------|----------------|------------|-------|
| **CF Workers** | `*.workers.dev` | N/A | ❌ Blocked (GFW) | REST API ✅, wrangler CLI ❌ | Deployable via API but unreachable from China |
| **CF Pages** | `*.pages.dev` | N/A | ✅ Works | REST API (needs Pages token) | Functions directory: `/functions/proxy.js` |
| **Deno Deploy** | `*.deno.dev` | N/A | ❓ Unknown | Signups unavailable (403) | 2026-08: account creation disabled |
| **Vercel** | `*.vercel.app` | N/A | ✅ Works | GitHub import (phone-friendly) | See `vercel-github-proxy-deploy.md` for full flow |
| **Netlify** | `*.netlify.app` | N/A | ✅ Works | Git push or CLI | Requires Netlify account |

## Deployment Strategy for Mobile Users

When deploying proxies, the user is on mobile (WeChat) and can only do browser-based actions:
1. **Create API tokens** — they can do this at dash.cloudflare.com/profile/api-tokens on mobile
2. **Deploy from server** — use the token to deploy via REST API from the Lighthouse server
3. **Never ask for CLI** — no `npm`, `npx`, `vercel login`, `wrangler deploy`

## Cloudflare Workers via REST API

### Proxy Code (service-worker format)

```js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(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 });
  }
}
```

### Deploy via REST API

```bash
# 1. Get account ID
curl -s -H "Authorization: Bearer $CF_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts" | python3 -m json.tool

# 2. Deploy Worker
ACCOUNT_ID="60fe57f5a5a310cd8073e41f82546c79"
curl -s -X PUT \
  "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/scripts/wechat-proxy" \
  -H "Authorization: Bearer $CF_TOKEN" \
  -H "Content-Type: application/javascript" \
  --data-binary @proxy.js

# 3. Get workers.dev subdomain
curl -s "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/workers/subdomain" \
  -H "Authorization: Bearer $CF_TOKEN"

# Result: https://wechat-proxy.<subdomain>.workers.dev
```

### ⚠️ workers.dev blocked from China

The deployed Worker at `*.workers.dev` is unreachable from Chinese servers (GFW DNS + IP block). Deployment succeeds (API accessible from China), but the proxy URL itself cannot be called from the Lighthouse server.

### wrangler CLI blocked from China

`npx wrangler@latest deploy` fails with "fetch failed" errors — wrangler connects to different Cloudflare endpoints that are blocked. Use REST API instead.

## Cloudflare Pages via REST API

`*.pages.dev` is accessible from China (verified with `curl`). Requires an API token with Pages permissions.

### Pages Function Code

Place in `/functions/proxy.js`:

```js
export async function onRequest(context) {
  const url = new URL(context.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(context.request.headers);
  headers.set("Host", new URL(target).host);
  headers.delete("origin");
  headers.delete("referer");

  try {
    const resp = await fetch(target, {
      method: context.request.method,
      headers,
      body: context.request.method === "POST" ? await context.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 });
  }
}
```

With `/functions/_routes.json`:
```json
{"version": 1, "include": ["/*"], "exclude": []}
```

### Token Permission Scopes

| Token Template | Workers API | Pages API | Zone Read |
|---------------|-------------|-----------|-----------|
| "Edit Cloudflare Workers" | ✅ | ❌ | ❌ |
| "Cloudflare Pages" | ❌ | ✅ | ❌ |
| Custom (both permissions) | ✅ | ✅ | Optional |

## Code Patches Required in we-mp-rss

The proxy support in `core/wx/base.py` only covers `content_extract()` (article body retrieval). The article LIST fetching in `core/wx/model/api.py` and `core/wx/model/web.py` makes direct `session.get()` calls without proxy. Both files need the same patch.

## Config Activation

```yaml
# config.yaml
proxy:
  enabled: ${PROXY_ENABLED:-False}
  deno_url: ${PROXY_DENO_URL:-}
```

```ini
# /etc/systemd/system/we-mp-rss.service
Environment=PROXY_ENABLED=True
Environment=PROXY_DENO_URL=https://wechat-proxy-a54.pages.dev
```

Also ensure `gather.model` is `api` (not `web`):
```yaml
gather:
  model: ${GATHER.MODEL:-api}  # NOT web — web uses wrong endpoint (appmsgpublish)
```
