CoolFace
Apppublic

hsbatakurki/HuggingMes

sourceHugging Facemitupdated 4mo agoView on Hugging Face
0likes
cloudflare-proxy-setup.py214 linesDownload Raw Back to root
1#!/usr/bin/env python32from __future__ import annotations3 4"""Create or reuse Cloudflare Workers for Telegram proxy and Space keep-awake."""5 6import json7import os8import re9import secrets10import sys11import time12import urllib.request13from pathlib import Path14 15API_BASE = "https://api.cloudflare.com/client/v4"16ENV_FILE = Path("/tmp/huggingmes-cloudflare-proxy.env")17ENV_FILE = Path("/tmp/huggingmes-cloudflare-proxy.env")18DEFAULT_ALLOWED = [19    # Messaging & social — primary use-case for Cloudflare proxy on HF Spaces20    # (geo-restrictions on Telegram, Discord, WhatsApp, etc.)21    "api.telegram.org",22    "discord.com",23    "discordapp.com",24    "gateway.discord.gg",25    "status.discord.com",26    "slack.com",27    "api.slack.com",28    "web.whatsapp.com",29    # Social — confirmed/likely blocked by HF firewall30    "graph.facebook.com",31    "graph.instagram.com",32    "api.twitter.com",33    "api.x.com",34    # Google35    "googleapis.com",36    "google.com",37    "googleusercontent.com",38    "gstatic.com",39    # Email HTTP APIs (SMTP ports are blocked)40    "api.resend.com",41    "api.sendgrid.com",42    # NOTE: AI-provider domains (api.openai.com, api.anthropic.com, etc.) are43    # intentionally NOT included here. Proxying AI calls routes API keys through44    # the Cloudflare Worker without explicit opt-in. Users who need AI API calls45    # proxied can add specific domains via CLOUDFLARE_PROXY_DOMAINS env var.46]47 48 49def cf_request(method: str, path: str, token: str, body: bytes | None = None, content_type: str = "application/json"):50    req = urllib.request.Request(51        f"{API_BASE}{path}",52        data=body,53        method=method,54        headers={"Authorization": f"Bearer {token}", "Content-Type": content_type},55    )56    with urllib.request.urlopen(req, timeout=30) as response:57        payload = json.loads(response.read().decode("utf-8"))58    if not payload.get("success"):59        errors = payload.get("errors") or [{"message": "Unknown Cloudflare API error"}]60        raise RuntimeError(errors[0].get("message", "Unknown Cloudflare API error"))61    return payload["result"]62 63 64def slugify(value: str) -> str:65    cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")66    cleaned = re.sub(r"-{2,}", "-", cleaned)67    return (cleaned or "huggingmes-proxy")[:63].rstrip("-")68 69 70def derive_worker_name() -> str:71    explicit = os.environ.get("CLOUDFLARE_WORKER_NAME", "").strip()72    if explicit:73        return slugify(explicit)74    space_host = os.environ.get("SPACE_HOST", "").strip()75    if space_host:76        return slugify(f"{space_host.replace('.hf.space', '')}-proxy")77    return "huggingmes-proxy"78 79 80def render_worker(secret_value: str, allowed_targets: list[str], allow_proxy_all: bool) -> str:81    return f"""addEventListener("fetch", (event) => {{82  event.respondWith(handleRequest(event.request));83}});84 85const PROXY_SHARED_SECRET = {json.dumps(secret_value)};86const ALLOW_PROXY_ALL = {"true" if allow_proxy_all else "false"};87const ALLOWED_TARGETS = {json.dumps(allowed_targets)};88 89function isAllowedHost(hostname) {{90  const normalized = String(hostname || "").trim().toLowerCase();91  if (!normalized) return false;92  if (ALLOW_PROXY_ALL) return true;93  return ALLOWED_TARGETS.some((domain) => normalized === domain || normalized.endsWith(`.${{domain}}`));94}}95 96async function handleRequest(request) {{97  const url = new URL(request.url);98  const queryTarget = url.searchParams.get("proxy_target");99  const targetHost = request.headers.get("x-target-host") || queryTarget;100 101  if (PROXY_SHARED_SECRET) {{102    const providedSecret = request.headers.get("x-proxy-key") || url.searchParams.get("proxy_key") || "";103    const telegramStylePath = url.pathname.startsWith("/bot") || url.pathname.startsWith("/file/bot");104    if (providedSecret !== PROXY_SHARED_SECRET && !(telegramStylePath && !targetHost)) {{105      return new Response("Unauthorized: Invalid proxy key", {{ status: 401 }});106    }}107  }}108 109  let targetBase = "";110  if (targetHost) {{111    if (!isAllowedHost(targetHost)) {{112      return new Response(`Forbidden: Host ${{targetHost}} is not allowed.`, {{ status: 403 }});113    }}114    targetBase = `https://${{targetHost}}`;115  }} else if (url.pathname.startsWith("/bot") || url.pathname.startsWith("/file/bot")) {{116    targetBase = "https://api.telegram.org";117  }} else {{118    return new Response("Invalid request: No target host provided.", {{ status: 400 }});119  }}120 121  const cleanSearch = new URLSearchParams(url.search);122  cleanSearch.delete("proxy_target");123  cleanSearch.delete("proxy_key");124  const searchStr = cleanSearch.toString();125  const targetUrl = targetBase + url.pathname + (searchStr ? `?${{searchStr}}` : "");126 127  const headers = new Headers(request.headers);128  for (const header of ["cf-connecting-ip", "cf-ray", "cf-visitor", "host", "x-real-ip", "x-target-host", "x-proxy-key"]) {{129    headers.delete(header);130  }}131 132  try {{133    return await fetch(new Request(targetUrl, {{134      method: request.method,135      headers,136      body: request.body,137      redirect: "follow",138    }}));139  }} catch (error) {{140    return new Response(`Proxy Error: ${{error.message}}`, {{ status: 502 }});141  }}142}}143"""144 145 146def write_env(proxy_url: str, proxy_secret: str) -> None:147    ENV_FILE.write_text(148        f'export CLOUDFLARE_PROXY_URL="{proxy_url}"\nexport CLOUDFLARE_PROXY_SECRET="{proxy_secret}"\n',149        encoding="utf-8",150    )151    ENV_FILE.chmod(0o600)152 153 154def resolve_account_and_subdomain(api_token: str) -> tuple[str, str]:155    account_id = os.environ.get("CLOUDFLARE_ACCOUNT_ID", "").strip()156    if not account_id:157        accounts = cf_request("GET", "/accounts", api_token)158        if not accounts:159            raise RuntimeError("No Cloudflare account is available for this token.")160        account_id = accounts[0]["id"]161 162    subdomain_info = cf_request("GET", f"/accounts/{account_id}/workers/subdomain", api_token)163    subdomain = (subdomain_info or {}).get("subdomain", "").strip()164    if not subdomain:165        raise RuntimeError("Cloudflare Workers subdomain is not configured. Enable workers.dev first.")166    return account_id, subdomain167 168 169def main() -> int:170    existing_url = os.environ.get("CLOUDFLARE_PROXY_URL", "").strip()171    existing_secret = os.environ.get("CLOUDFLARE_PROXY_SECRET", "").strip()172    api_token = os.environ.get("CLOUDFLARE_WORKERS_TOKEN", "").strip()173 174    if existing_url:175        write_env(existing_url, existing_secret)176 177    if not api_token:178        return 0179 180    try:181        account_id, subdomain = resolve_account_and_subdomain(api_token)182 183        if not existing_url:184            allowed_raw = os.environ.get("CLOUDFLARE_PROXY_DOMAINS", "").strip()185            allow_proxy_all = allowed_raw == "*"186            extra = [] if allow_proxy_all else [v.strip() for v in allowed_raw.split(",") if v.strip()]187            allowed = list(dict.fromkeys(DEFAULT_ALLOWED + extra))188            worker_name = derive_worker_name()189            proxy_secret = existing_secret or secrets.token_urlsafe(24)190 191            cf_request(192                "PUT",193                f"/accounts/{account_id}/workers/scripts/{worker_name}",194                api_token,195                body=render_worker(proxy_secret, allowed, allow_proxy_all).encode("utf-8"),196                content_type="application/javascript",197            )198            cf_request(199                "POST",200                f"/accounts/{account_id}/workers/scripts/{worker_name}/subdomain",201                api_token,202                body=json.dumps({"enabled": True, "previews_enabled": True}).encode("utf-8"),203            )204            write_env(f"https://{worker_name}.{subdomain}.workers.dev", proxy_secret)205 206        return 0207    except Exception as exc:208        print(f"Cloudflare proxy setup failed: {exc}", file=sys.stderr)209        return 1210 211 212if __name__ == "__main__":213    raise SystemExit(main())214