jarvis-cloud / backend /routes /internet_routes.py
Jarvis2345's picture
deploy(S4): Blender headless pipeline + WebAR client + backend fixes
a1fcfb8 verified
Raw
History Blame Contribute Delete
4.38 kB
"""
backend/routes/internet_routes.py
Β§2.4 β€” Internet Layer: live OSINT, web search, URL fetch, scrape
"""
from fastapi import HTTPException, APIRouter, Depends
from pydantic import BaseModel
from typing import Optional
from backend.billing.gating import require_feature
from backend.billing.plans import Feature
router = APIRouter()
# v22: OSINT is a paid feature in the plan catalog (plus/pro), WEB_SEARCH is not (free
# grants it). So the gate goes on /osint specifically, NOT on the router β€” gating the
# whole router would take /search away from the free tier that is entitled to it.
_OSINT_GATE = Depends(require_feature(Feature.OSINT))
class SearchRequest(BaseModel):
query: str
num_results: int = 5
persona: Optional[str] = "jarvis"
class FetchRequest(BaseModel):
url: str
summarise: bool = True
class OsintRequest(BaseModel):
target: str # domain, IP, username, email, etc.
recon_type: str = "full" # full | whois | social | dns | email
@router.post("/search")
async def web_search(req: SearchRequest):
"""Live OSINT web search via the backend tool registry."""
try:
from backend.tools.web_search_tools import search_web
results = await search_web(req.query, num_results=req.num_results)
return {"status": "ok", "query": req.query, "results": results}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/fetch")
async def fetch_url(req: FetchRequest):
"""Fetch and optionally summarise a URL using the browser tool."""
try:
# browser_tools has never exported `fetch_url` (it exposes navigate /
# get_ephemeral_context), so this route was a guaranteed 500:
# "cannot import name 'fetch_url' from 'backend.tools.browser_tools'".
# The function that actually does this job β€” Playwright fetch, CAPTCHA
# handling, text extraction β€” is web_search_tools.fetch_page.
from backend.tools.web_search_tools import fetch_page
content = await fetch_page(req.url)
return {"status": "ok", "url": req.url, "content": content}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/osint")
async def osint_recon(req: OsintRequest, _user: str = _OSINT_GATE):
"""
Elite Defensive OSINT recon endpoint.
Performs web reconnaissance, digital footprint analysis,
and threat modelling on a given target.
"""
if not req.target:
raise HTTPException(status_code=400, detail="target is required")
try:
from backend.tools.web_search_tools import search_web
queries = {
"full": [f'site:shodan.io "{req.target}"', f'"{req.target}" breach OR leak OR hack', f'"{req.target}" whois DNS records'],
"whois": [f'whois "{req.target}"'],
"social": [f'"{req.target}" site:twitter.com OR site:linkedin.com OR site:github.com'],
"dns": [f'DNS records "{req.target}"'],
"email": [f'"{req.target}" email breach haveibeenpwned'],
}.get(req.recon_type, [f'"{req.target}"'])
all_results = []
for q in queries:
results = await search_web(q, num_results=3)
# search_web returns {"results": [...]} (or {"error": ...}).
# extend()ing the dict itself appended its KEYS β€” so findings came
# back as ["results", "results", ...] instead of the actual hits.
if isinstance(results, dict):
all_results.extend(results.get("results", []))
elif isinstance(results, list):
all_results.extend(results)
return {
"status": "ok",
"target": req.target,
"recon_type": req.recon_type,
"findings": all_results,
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/status")
async def internet_status():
"""Health check: verify the internet layer is reachable."""
import httpx
try:
async with httpx.AsyncClient(timeout=5) as client:
r = await client.get("https://www.google.com")
return {"status": "online", "latency_ms": r.elapsed.total_seconds() * 1000}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Internet layer offline: {e}")