File size: 4,802 Bytes
e0cbd7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "name": "RM API Proxy",
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# RM API Proxy — Colab Backup\n",
    "\n",
    "Runs a FastAPI proxy on Colab that forwards requests to rentmasseur.com.\n",
    "Bypasses Cloudflare IP blocks on GitHub Actions.\n",
    "\n",
    "**Setup:**\n",
    "1. Run all cells\n",
    "2. Copy the ngrok URL\n",
    "3. Set it as `PROXY_URL` GitHub secret"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install fastapi uvicorn pyngrok httpx -q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "PROXY_SECRET = 'rm-proxy-2026'  # @param {type:'string'}\n",
    "NGROK_AUTHTOKEN = ''  # @param {type:'string'}\n",
    "\n",
    "from pyngrok import ngrok\n",
    "ngrok.set_auth_token(NGROK_AUTHTOKEN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%writefile proxy_app.py\n",
    "import os, httpx\n",
    "from fastapi import FastAPI, Request, Response, HTTPException\n",
    "from fastapi.middleware.cors import CORSMiddleware\n",
    "\n",
    "UPSTREAM = 'https://rentmasseur.com'\n",
    "PROXY_SECRET = os.environ.get('PROXY_SECRET', 'rm-proxy-2026')\n",
    "\n",
    "app = FastAPI()\n",
    "app.add_middleware(CORSMiddleware, allow_origins=['*'], allow_methods=['*'], allow_headers=['*'])\n",
    "\n",
    "@app.get('/')\n",
    "async def health():\n",
    "    return {'status': 'ok', 'proxy': 'rm-api-colab'}\n",
    "\n",
    "@app.api_route('/{path:path}', methods=['GET','POST','PUT','DELETE','PATCH','OPTIONS'])\n",
    "async def proxy(path: str, request: Request):\n",
    "    client_secret = request.headers.get('X-Proxy-Secret', '')\n",
    "    if client_secret != PROXY_SECRET:\n",
    "        raise HTTPException(status_code=401, detail='unauthorized')\n",
    "    upstream_url = f'{UPSTREAM}/{path}'\n",
    "    if request.url.query:\n",
    "        upstream_url += f'?{request.url.query}'\n",
    "    headers = {\n",
    "        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',\n",
    "        'Accept': 'application/json, text/plain, */*',\n",
    "        'Accept-Language': 'en-US,en;q=0.9',\n",
    "        'Origin': UPSTREAM,\n",
    "        'Referer': UPSTREAM + '/settings',\n",
    "    }\n",
    "    auth = request.headers.get('Authorization')\n",
    "    if auth:\n",
    "        headers['Authorization'] = auth\n",
    "    ct = request.headers.get('Content-Type')\n",
    "    if ct:\n",
    "        headers['Content-Type'] = ct\n",
    "    body = None\n",
    "    if request.method not in ('GET','HEAD','OPTIONS'):\n",
    "        body = await request.body()\n",
    "    async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:\n",
    "        try:\n",
    "            resp = await client.request(method=request.method, url=upstream_url, headers=headers, content=body)\n",
    "            return Response(content=resp.content, status_code=resp.status_code, headers=dict(resp.headers), media_type=resp.headers.get('content-type','application/json'))\n",
    "        except httpx.TimeoutException:\n",
    "            raise HTTPException(status_code=504, detail='upstream timeout')\n",
    "        except Exception as e:\n",
    "            raise HTTPException(status_code=502, detail=f'proxy error: {str(e)}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import subprocess, threading, time, os\n",
    "os.environ['PROXY_SECRET'] = PROXY_SECRET\n",
    "\n",
    "# Start uvicorn in background\n",
    "def run_server():\n",
    "    os.system('uvicorn proxy_app:app --host 0.0.0.0 --port 8000 &')\n",
    "\n",
    "threading.Thread(target=run_server, daemon=True).start()\n",
    "time.sleep(3)\n",
    "\n",
    "# Expose via ngrok\n",
    "public_url = ngrok.connect(8000)\n",
    "print(f'✅ Proxy URL: {public_url}')\n",
    "print(f'Set PROXY_URL secret to: {public_url}')\n",
    "print(f'PROXY_SECRET: {PROXY_SECRET}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test the proxy\n",
    "import requests\n",
    "r = requests.get(str(public_url) + '/', headers={'X-Proxy-Secret': PROXY_SECRET})\n",
    "print(f'Health: {r.status_code} {r.json()}')"
   ]
  }
 ]
}