{ "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()}')" ] } ] }