- Add LICENSE file (public domain, permacomputer values)
- Add license headers to all 27 Python source files
- Restore full about_ch9_p2 with "terminated 🟣" content
- Translate about_ch9_p2 to all 26 languages
The permacomputer is community-owned infrastructure optimized around:
TRUTH, FREEDOM, HARMONY, LOVE
138 lines
4.7 KiB
Python
138 lines
4.7 KiB
Python
# PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
|
|
#
|
|
# This is free public domain software for the public good of a permacomputer hosted
|
|
# at permacomputer.com - an always-on computer by the people, for the people. One
|
|
# which is durable, easy to repair, and distributed like tap water for machine
|
|
# learning intelligence.
|
|
#
|
|
# The permacomputer is community-owned infrastructure optimized around four values:
|
|
#
|
|
# TRUTH - First principles, math & science, open source code freely distributed
|
|
# FREEDOM - Voluntary partnerships, freedom from tyranny & corporate control
|
|
# HARMONY - Minimal waste, self-renewing systems with diverse thriving connections
|
|
# LOVE - Be yourself without hurting others, cooperation through natural law
|
|
#
|
|
# Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
# software, either in source code form or as a compiled binary, for any purpose,
|
|
# commercial or non-commercial, and by any means.
|
|
#
|
|
# NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
|
|
#
|
|
# Copyright 2025 TimeHexOn & foxhop & russell@unturf
|
|
# https://git.unturf.com/engineering/unturf/pig.py
|
|
|
|
"""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, Cookie, Header
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from .i18n import TRANSLATIONS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Create app
|
|
app = FastAPI(title="neopig", description="Media crawler + SERP + Screenshot service")
|
|
|
|
# Supported language codes
|
|
SUPPORTED_LANGS = set(TRANSLATIONS.keys())
|
|
|
|
|
|
async def get_language(
|
|
request: Request,
|
|
lang: str = Cookie(None),
|
|
accept_language: str = Header(None)
|
|
) -> str:
|
|
"""Dependency: resolve language from ?lang=XX > cookie > Accept-Language.
|
|
|
|
Easter egg #0: Use ?lang=XX to force any page into a specific language.
|
|
Cookie is set via middleware (LanguageCookieMiddleware).
|
|
"""
|
|
# Query param takes priority
|
|
lang_query = request.query_params.get("lang")
|
|
if lang_query and lang_query.lower() in SUPPORTED_LANGS:
|
|
return lang_query.lower()
|
|
|
|
# Cookie next
|
|
if lang and lang in SUPPORTED_LANGS:
|
|
return lang
|
|
|
|
# Accept-Language header
|
|
if accept_language:
|
|
for part in accept_language.split(','):
|
|
code = part.split(';')[0].strip().split('-')[0].lower()
|
|
if code in SUPPORTED_LANGS:
|
|
return code
|
|
|
|
return "en"
|
|
|
|
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
|
|
class LanguageCookieMiddleware(BaseHTTPMiddleware):
|
|
"""Middleware to set lang cookie when ?lang=XX is used."""
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
response = await call_next(request)
|
|
# Set cookie if ?lang= query param is present and valid
|
|
lang_query = request.query_params.get("lang")
|
|
if lang_query and lang_query.lower() in SUPPORTED_LANGS:
|
|
response.set_cookie(
|
|
key="lang",
|
|
value=lang_query.lower(),
|
|
max_age=365 * 24 * 60 * 60,
|
|
httponly=False,
|
|
samesite="lax"
|
|
)
|
|
return response
|
|
|
|
|
|
app.add_middleware(LanguageCookieMiddleware)
|
|
|
|
# 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")
|