mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 04:57:15 -05:00
415 lines
11 KiB
Python
415 lines
11 KiB
Python
"""
|
|
RoadView Browser API Router
|
|
|
|
Provides web browsing capabilities:
|
|
- URL fetching with proxy
|
|
- Bookmark management
|
|
- Browsing history
|
|
- Search engine integration
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from typing import List, Optional
|
|
import httpx
|
|
from urllib.parse import urlparse, quote
|
|
import hashlib
|
|
|
|
from ..database import get_db
|
|
from ..auth import get_current_user
|
|
from ..models import User
|
|
from pydantic import BaseModel, HttpUrl
|
|
from ..utils import utc_now
|
|
|
|
router = APIRouter(prefix="/api/browser", tags=["browser"])
|
|
|
|
|
|
class Bookmark(BaseModel):
|
|
title: str
|
|
url: str
|
|
folder: Optional[str] = "Bookmarks"
|
|
|
|
|
|
class HistoryEntry(BaseModel):
|
|
url: str
|
|
title: str
|
|
|
|
|
|
@router.get("/fetch")
|
|
async def fetch_url(
|
|
url: str = Query(..., description="URL to fetch"),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Fetch a web page through proxy
|
|
Returns HTML content with modified links for proxy routing
|
|
"""
|
|
try:
|
|
# Validate URL
|
|
parsed = urlparse(url)
|
|
if not parsed.scheme:
|
|
url = f"https://{url}"
|
|
|
|
async with httpx.AsyncClient(
|
|
timeout=30.0,
|
|
follow_redirects=True,
|
|
headers={
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"
|
|
}
|
|
) as client:
|
|
response = await client.get(url)
|
|
|
|
# Get content type
|
|
content_type = response.headers.get("content-type", "text/html")
|
|
|
|
return {
|
|
"url": str(response.url),
|
|
"status_code": response.status_code,
|
|
"content_type": content_type,
|
|
"content": response.text if "text" in content_type else None,
|
|
"headers": dict(response.headers),
|
|
"is_html": "text/html" in content_type
|
|
}
|
|
|
|
except httpx.TimeoutException:
|
|
raise HTTPException(status_code=408, detail="Request timeout")
|
|
except httpx.RequestError as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to fetch URL: {str(e)}")
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Error fetching URL: {str(e)}")
|
|
|
|
|
|
@router.get("/bookmarks")
|
|
async def get_bookmarks(
|
|
folder: Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get user's bookmarks"""
|
|
# In production, this would be stored in a Bookmarks table
|
|
# For now, returning demo bookmarks
|
|
demo_bookmarks = [
|
|
{
|
|
"id": 1,
|
|
"title": "GitHub",
|
|
"url": "https://github.com",
|
|
"folder": "Development",
|
|
"created_at": "2024-01-01T00:00:00Z"
|
|
},
|
|
{
|
|
"id": 2,
|
|
"title": "Stack Overflow",
|
|
"url": "https://stackoverflow.com",
|
|
"folder": "Development",
|
|
"created_at": "2024-01-02T00:00:00Z"
|
|
},
|
|
{
|
|
"id": 3,
|
|
"title": "Hacker News",
|
|
"url": "https://news.ycombinator.com",
|
|
"folder": "News",
|
|
"created_at": "2024-01-03T00:00:00Z"
|
|
},
|
|
{
|
|
"id": 4,
|
|
"title": "Hugging Face",
|
|
"url": "https://huggingface.co",
|
|
"folder": "AI/ML",
|
|
"created_at": "2024-01-04T00:00:00Z"
|
|
}
|
|
]
|
|
|
|
if folder:
|
|
demo_bookmarks = [b for b in demo_bookmarks if b["folder"] == folder]
|
|
|
|
return {
|
|
"bookmarks": demo_bookmarks,
|
|
"folders": ["Development", "News", "AI/ML"]
|
|
}
|
|
|
|
|
|
@router.post("/bookmarks")
|
|
async def add_bookmark(
|
|
bookmark: Bookmark,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Add a new bookmark"""
|
|
# In production, save to database
|
|
new_bookmark = {
|
|
"id": hashlib.md5(bookmark.url.encode()).hexdigest()[:8],
|
|
"title": bookmark.title,
|
|
"url": bookmark.url,
|
|
"folder": bookmark.folder,
|
|
"created_at": utc_now().isoformat()
|
|
}
|
|
|
|
return {
|
|
"message": "Bookmark added",
|
|
"bookmark": new_bookmark
|
|
}
|
|
|
|
|
|
@router.delete("/bookmarks/{bookmark_id}")
|
|
async def delete_bookmark(
|
|
bookmark_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Delete a bookmark"""
|
|
return {"message": "Bookmark deleted"}
|
|
|
|
|
|
@router.get("/history")
|
|
async def get_history(
|
|
limit: int = Query(50, ge=1, le=1000),
|
|
offset: int = Query(0, ge=0),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get browsing history"""
|
|
# In production, this would be stored in a BrowserHistory table
|
|
demo_history = [
|
|
{
|
|
"id": 1,
|
|
"url": "https://github.com/trending",
|
|
"title": "Trending - GitHub",
|
|
"visited_at": "2024-01-10T15:30:00Z",
|
|
"visit_count": 5
|
|
},
|
|
{
|
|
"id": 2,
|
|
"url": "https://stackoverflow.com/questions/tagged/python",
|
|
"title": "Newest Python Questions - Stack Overflow",
|
|
"visited_at": "2024-01-10T14:20:00Z",
|
|
"visit_count": 2
|
|
},
|
|
{
|
|
"id": 3,
|
|
"url": "https://news.ycombinator.com",
|
|
"title": "Hacker News",
|
|
"visited_at": "2024-01-10T13:15:00Z",
|
|
"visit_count": 12
|
|
}
|
|
]
|
|
|
|
return {
|
|
"history": demo_history[offset:offset+limit],
|
|
"total": len(demo_history)
|
|
}
|
|
|
|
|
|
@router.post("/history")
|
|
async def add_history_entry(
|
|
entry: HistoryEntry,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Add a history entry"""
|
|
new_entry = {
|
|
"id": hashlib.md5(f"{entry.url}{utc_now()}".encode()).hexdigest()[:8],
|
|
"url": entry.url,
|
|
"title": entry.title,
|
|
"visited_at": utc_now().isoformat()
|
|
}
|
|
|
|
return {
|
|
"message": "History entry added",
|
|
"entry": new_entry
|
|
}
|
|
|
|
|
|
@router.delete("/history")
|
|
async def clear_history(
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Clear all browsing history"""
|
|
return {"message": "History cleared"}
|
|
|
|
|
|
@router.get("/search")
|
|
async def web_search(
|
|
q: str = Query(..., min_length=1, description="Search query"),
|
|
engine: str = Query("duckduckgo", pattern="^(duckduckgo|google|bing)$"),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Perform a web search using the specified search engine
|
|
Returns redirect URL to search results
|
|
"""
|
|
search_urls = {
|
|
"duckduckgo": f"https://duckduckgo.com/?q={quote(q)}",
|
|
"google": f"https://www.google.com/search?q={quote(q)}",
|
|
"bing": f"https://www.bing.com/search?q={quote(q)}"
|
|
}
|
|
|
|
return {
|
|
"query": q,
|
|
"engine": engine,
|
|
"url": search_urls[engine]
|
|
}
|
|
|
|
|
|
@router.get("/quicklinks")
|
|
async def get_quicklinks(
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get quick access links (like a speed dial)"""
|
|
return {
|
|
"quicklinks": [
|
|
{
|
|
"title": "GitHub",
|
|
"url": "https://github.com",
|
|
"icon": "🐙",
|
|
"category": "Development"
|
|
},
|
|
{
|
|
"title": "Stack Overflow",
|
|
"url": "https://stackoverflow.com",
|
|
"icon": "📚",
|
|
"category": "Development"
|
|
},
|
|
{
|
|
"title": "Hacker News",
|
|
"url": "https://news.ycombinator.com",
|
|
"icon": "📰",
|
|
"category": "News"
|
|
},
|
|
{
|
|
"title": "Hugging Face",
|
|
"url": "https://huggingface.co",
|
|
"icon": "🤗",
|
|
"category": "AI"
|
|
},
|
|
{
|
|
"title": "Reddit",
|
|
"url": "https://reddit.com",
|
|
"icon": "🔴",
|
|
"category": "Social"
|
|
},
|
|
{
|
|
"title": "YouTube",
|
|
"url": "https://youtube.com",
|
|
"icon": "▶️",
|
|
"category": "Video"
|
|
},
|
|
{
|
|
"title": "Wikipedia",
|
|
"url": "https://wikipedia.org",
|
|
"icon": "📖",
|
|
"category": "Reference"
|
|
},
|
|
{
|
|
"title": "DigitalOcean",
|
|
"url": "https://digitalocean.com",
|
|
"icon": "🌊",
|
|
"category": "Cloud"
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
@router.get("/settings")
|
|
async def get_browser_settings(
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get browser settings"""
|
|
return {
|
|
"settings": {
|
|
"default_search_engine": "duckduckgo",
|
|
"homepage": "about:newtab",
|
|
"enable_javascript": True,
|
|
"enable_cookies": True,
|
|
"enable_images": True,
|
|
"user_agent": "RoadView/1.0 (BlackRoad OS)"
|
|
}
|
|
}
|
|
|
|
|
|
@router.put("/settings")
|
|
async def update_browser_settings(
|
|
settings: dict,
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Update browser settings"""
|
|
return {
|
|
"message": "Settings updated",
|
|
"settings": settings
|
|
}
|
|
|
|
|
|
@router.get("/download")
|
|
async def download_file(
|
|
url: str = Query(..., description="File URL to download"),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Download a file through proxy
|
|
Returns file metadata and download token
|
|
"""
|
|
try:
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
# HEAD request to get file info
|
|
response = await client.head(url)
|
|
|
|
content_type = response.headers.get("content-type", "application/octet-stream")
|
|
content_length = response.headers.get("content-length", "unknown")
|
|
|
|
filename = url.split("/")[-1] or "download"
|
|
|
|
return {
|
|
"url": url,
|
|
"filename": filename,
|
|
"content_type": content_type,
|
|
"size": content_length,
|
|
"message": "File info retrieved. In production, this would initiate a download."
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"Failed to access file: {str(e)}")
|
|
|
|
|
|
@router.get("/tabs")
|
|
async def get_open_tabs(
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Get currently open tabs (session management)"""
|
|
# In production, store in Redis for session management
|
|
return {
|
|
"tabs": [
|
|
{
|
|
"id": 1,
|
|
"url": "https://github.com",
|
|
"title": "GitHub",
|
|
"active": True
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
@router.post("/tabs")
|
|
async def open_new_tab(
|
|
url: str,
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Open a new tab"""
|
|
return {
|
|
"tab": {
|
|
"id": hashlib.md5(f"{url}{utc_now()}".encode()).hexdigest()[:8],
|
|
"url": url,
|
|
"title": "Loading...",
|
|
"active": True
|
|
}
|
|
}
|
|
|
|
|
|
@router.delete("/tabs/{tab_id}")
|
|
async def close_tab(
|
|
tab_id: str,
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""Close a tab"""
|
|
return {"message": "Tab closed"}
|