Terminal / api /web.py
Pulka
feat(telegram): bot UX 100% — WebApp button, /logs, /ping, inline query, _BACK_KB
4cbd6c4 verified
Raw
History Blame
2.99 kB
"""api/web.py — Web search + fetch endpoints
V7-2 GAP-2b: aggiunto /proxy-fetch — server-side fetch zero CORS via Railway.
"""
from fastapi import APIRouter
from pydantic import BaseModel
from typing import Optional
from tools.web_search import web_search
from tools.web_fetch import fetch_page
from tools.ranking import rank_results, format_for_llm
from tools.content_cleaner import clean_and_structure
router=APIRouter(prefix="/web",tags=["web"])
class SearchReq(BaseModel):
query:str; focus:Optional[str]="general"; max_results:Optional[int]=6; format:Optional[str]="json"
class FetchReq(BaseModel):
url:str; query:Optional[str]=None; max_chars:Optional[int]=5000
class ProxyFetchReq(BaseModel):
"""V7-2 GAP-2b: server-side proxy fetch — zero CORS, nessun WAF client-side."""
url: str
method: Optional[str] = "GET"
headers: Optional[dict] = None
body: Optional[str] = None
max_chars: Optional[int] = 50_000
@router.post("/search")
async def search(req:SearchReq):
raw=await web_search(req.query,req.focus or "general",req.max_results or 6)
ranked=rank_results(raw.get("results",[]),req.query,top_k=req.max_results or 6)
if req.format=="text": return {"text":format_for_llm(ranked,req.query),"query":req.query}
return {**raw,"results":ranked}
@router.post("/fetch")
async def fetch(req:FetchReq):
raw=await fetch_page(req.url,max_chars=req.max_chars or 5000)
if req.query and raw.get("content"):
cleaned=clean_and_structure(raw["content"],req.query)
raw["content"]=cleaned["content"]; raw["words"]=cleaned["words"]
return raw
@router.post("/proxy-fetch")
async def proxy_fetch(req: ProxyFetchReq):
"""
V7-2 GAP-2b: server-side proxy fetch.
Il client iPhone Safari fallisce su ~40-60% dei fetch diretti (CORS, WAF, TLS fingerprint).
Questo endpoint esegue il fetch lato server (Railway) e lo ritorna al client:
- Zero CORS (server-to-server)
- Bypassa WAF client-side
- httpx con timeout 7s e max_chars 50K
- Response: {ok:bool, status:int, body:str, url:str} o {ok:false, error:str, url:str}
Cold-start Railway free tier: fino a 5s → il client usa timeout 7s.
"""
try:
import httpx
max_chars = min(req.max_chars or 50_000, 100_000) # hard cap 100K
hdrs = req.headers or {}
# Imposta un User-Agent realistico per evitare blocchi anti-bot
if "user-agent" not in {k.lower() for k in hdrs}:
hdrs = {**hdrs, "User-Agent": "Mozilla/5.0 (compatible; AgentBot/1.0)"}
async with httpx.AsyncClient(timeout=7.0, follow_redirects=True) as client:
resp = await client.request(
method = (req.method or "GET").upper(),
url = req.url,
headers = hdrs,
content = req.body.encode("utf-8") if req.body else None,
)
body = resp.text[:max_chars]
return {"ok": True, "status": resp.status_code, "body": body, "url": req.url}
except Exception as e:
return {"ok": False, "error": str(e)[:300], "url": req.url}