112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
"""FastAPI application setup for SERP.
|
|
# Side quest 4/21: The rabbit hole goes deeper than you think.
|
|
|
|
# Down the rabbit whole, Alice - Gulp!
|
|
#
|
|
# If you're reading this, you understand Python.
|
|
# The source code for neopig is Public Domain. Steal what works for you!
|
|
#
|
|
# - The Sign Maker
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import RedirectResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from miniuri import Uri
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create app
|
|
app = FastAPI(title="neopig", description="Media crawler + SERP + Screenshot service")
|
|
|
|
|
|
class LanguageMiddleware(BaseHTTPMiddleware):
|
|
"""Middleware to handle ?lang=XX query param for language switching.
|
|
|
|
Easter egg #0: Use ?lang=XX to force any page into a specific language.
|
|
Example: /?lang=ka forces Georgian, /?lang=ja forces Japanese.
|
|
|
|
When ?lang=XX is present:
|
|
1. Validates against supported languages
|
|
2. Sets the 'lang' cookie
|
|
3. Redirects to same URL without the lang param
|
|
"""
|
|
|
|
# Supported language codes (must match TRANSLATIONS keys)
|
|
SUPPORTED_LANGS = {
|
|
"en", "zh", "zh-tw", "es", "hi", "ar", "pt", "ru", "ja", "fr", "de",
|
|
"ko", "it", "nl", "pl", "tr", "vi", "th", "id", "uk", "sv",
|
|
"bn", "ur", "sw", "mr", "te", "ka"
|
|
}
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
# Check for lang query param
|
|
lang_param = request.query_params.get("lang")
|
|
|
|
if lang_param and lang_param.lower() in self.SUPPORTED_LANGS:
|
|
# Build redirect URL without lang param using miniuri
|
|
uri = Uri(str(request.url))
|
|
# Remove lang from query params
|
|
new_query = "&".join(
|
|
f"{k}={v}" for k, v in request.query_params.items() if k != "lang"
|
|
)
|
|
new_url = f"{uri.scheme}://{uri.authority}{uri.path}"
|
|
if new_query:
|
|
new_url += f"?{new_query}"
|
|
|
|
# Redirect with cookie set
|
|
response = RedirectResponse(url=new_url, status_code=302)
|
|
response.set_cookie(
|
|
key="lang",
|
|
value=lang_param.lower(),
|
|
max_age=365 * 24 * 60 * 60, # 1 year
|
|
httponly=False, # Allow JS access for dropdown
|
|
samesite="lax"
|
|
)
|
|
return response
|
|
|
|
return await call_next(request)
|
|
|
|
|
|
# Add language middleware
|
|
app.add_middleware(LanguageMiddleware)
|
|
|
|
# Paths relative to serp.py location (parent of this package)
|
|
BASE_PATH = Path(__file__).parent.parent
|
|
|
|
# Set up Jinja2 templates
|
|
TEMPLATES_PATH = BASE_PATH / "templates"
|
|
templates = Jinja2Templates(directory=str(TEMPLATES_PATH)) if TEMPLATES_PATH.exists() else None
|
|
|
|
# Try to include uri2png screenshot router (optional)
|
|
try:
|
|
from uri2png import get_screenshot_router
|
|
app.include_router(get_screenshot_router())
|
|
logger.info("Screenshot router loaded from uri2png")
|
|
except ImportError:
|
|
pass # Optional dependency
|
|
|
|
# Mount static files
|
|
STATIC_CSS_PATH = BASE_PATH / "static" / "css"
|
|
if STATIC_CSS_PATH.exists():
|
|
app.mount("/static/css", StaticFiles(directory=STATIC_CSS_PATH), name="css")
|
|
|
|
STATIC_VENDOR_PATH = BASE_PATH / "static" / "vendor"
|
|
if STATIC_VENDOR_PATH.exists():
|
|
app.mount("/static/vendor", StaticFiles(directory=STATIC_VENDOR_PATH), name="vendor")
|
|
|
|
STATIC_IMAGES_PATH = BASE_PATH / "static" / "images"
|
|
if STATIC_IMAGES_PATH.exists():
|
|
app.mount("/static/images", StaticFiles(directory=STATIC_IMAGES_PATH), name="images")
|
|
|
|
# Config
|
|
DB_PATH = "data/neopig.db"
|
|
VAULT_PATH = Path("data/vault")
|
|
CRAWL_DISABLED = os.environ.get("NEOPIG_DISABLE_CRAWL", "").lower() in ("1", "true", "yes")
|
|
IMPORT_MODE = os.environ.get("NEOPIG_IMPORT", "").lower() in ("1", "true", "yes")
|