XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
from __future__ import annotations

import os
import requests
from datetime import datetime
from urllib.parse import quote, unquote
from flask import send_from_directory, redirect, request

from ...image.copy_images import secure_filename
from ...cookies import get_cookies_dir
from ...errors import VersionNotFoundError
from ...config import STATIC_URL, DOWNLOAD_URL, DIST_DIR, GITHUB_URL
from ... import version


def redirect_home():
    return redirect("/chat/")


def render(filename="home", download_url: str = GITHUB_URL):
    if download_url == GITHUB_URL:
        filename += "" if "." in filename else ".html"
    html = None
    is_temp = False
    if os.path.exists(DIST_DIR) and not request.args.get("debug"):
        base_dir = os.path.abspath(os.path.dirname(DIST_DIR))
        path = os.path.abspath(os.path.join(base_dir, filename))
        if not path.startswith(base_dir + os.sep) and path != base_dir:
            return redirect("/")
        if os.path.exists(path):
            if path.endswith(".html"):
                try:
                    latest_version = version.utils.latest_version
                except VersionNotFoundError:
                    latest_version = version.utils.current_version
                with open(path, "r", encoding="utf-8") as f:
                    html = f.read()
                return html.replace("{{ v }}", str(latest_version))
            return send_from_directory(
                os.path.dirname(path), os.path.basename(path), max_age=31536000
            )
    try:
        latest_version = version.utils.latest_version
    except VersionNotFoundError:
        latest_version = version.utils.current_version
    today = datetime.today().strftime("%Y-%m-%d")
    cache_dir = os.path.join(get_cookies_dir(), ".gui_cache", today)
    if not request.args.get("g4f_session"):
        latest_version = str(latest_version) + quote(
            unquote(request.query_string.decode())
        )
    cache_file = os.path.join(
        cache_dir,
        f"{secure_filename(f'{version.utils.current_version}-{latest_version}')}.{secure_filename(filename)}",
    )
    if os.path.isfile(cache_file + ".js"):
        cache_file += ".js"
    if not os.path.exists(cache_file):
        if os.access(cache_file, os.W_OK):
            is_temp = True
        else:
            os.makedirs(cache_dir, exist_ok=True)
        if html is None:
            try:
                response = requests.get(f"{download_url}{filename}")
                response.raise_for_status()
            except requests.exceptions.SSLError:
                response = requests.get(f"{download_url}{filename}", verify=False)
                response.raise_for_status()
            except requests.RequestException:
                try:
                    response = requests.get(f"{DOWNLOAD_URL}{filename}")
                    response.raise_for_status()
                except requests.exceptions.SSLError:
                    response = requests.get(f"{DOWNLOAD_URL}{filename}", verify=False)
                    response.raise_for_status()
                except requests.RequestException:
                    found = None
                    for root, _, files in os.walk(cache_dir):
                        for file in files:
                            if file.startswith(secure_filename(filename)):
                                found = os.path.abspath(root), file
                                break
                        if found:
                            break
                    if found:
                        return send_from_directory(found[0], found[1], max_age=31536000)
                    else:
                        raise
            if not cache_file.endswith(".js") and response.headers.get(
                "Content-Type", ""
            ).startswith("application/javascript"):
                cache_file += ".js"
            if filename.endswith(".html"):
                html = response.text
                dist_url = (
                    "/dist/" if os.path.exists(DIST_DIR) else f"{STATIC_URL}dist/"
                )
                html = html.replace("'../dist/", f"'{dist_url}")
                html = html.replace("'/dist/", f"'{dist_url}")
                html = html.replace("'dist/", f"'{dist_url}")
                html = html.replace('<base href="/">', f'<base href="/sillytavern/">')
        if html is None:
            with open(cache_file, "wb") as f:
                f.write(response.content)
        else:
            html = html.replace("{{ v }}", latest_version)
            if is_temp:
                return html
            with open(cache_file, "w", encoding="utf-8") as f:
                f.write(html)
    return send_from_directory(
        os.path.abspath(cache_dir), os.path.basename(cache_file), max_age=31536000
    )


class Website:
    def __init__(self, app) -> None:
        self.app = app
        self.routes = {
            "/": {"function": self._index, "methods": ["GET", "POST"]},
            "/chat/": {"function": self._chat, "methods": ["GET", "POST"]},
            "/<filename>.html": {"function": self._index, "methods": ["GET", "POST"]},
            "/chat/<filename>": {"function": self._chat, "methods": ["GET", "POST"]},
            "/private/": {"function": self._private, "methods": ["GET", "POST"]},
            "/private/<path:filename>": {
                "function": self._private,
                "methods": ["GET", "POST"],
            },
            "/media/": {"function": redirect_home, "methods": ["GET", "POST"]},
            "/dist/<path:name>": {"function": self._dist, "methods": ["GET"]},
            "/playground/": {"function": self._playground, "methods": ["GET"]},
            "/playground/<path:filename>": {
                "function": self._playground,
                "methods": ["GET"],
            },
            "/sillytavern/": {"function": self._sillytavern, "methods": ["GET"]},
            "/sillytavern/<path:filename>": {
                "function": self._sillytavern,
                "methods": ["GET"],
            },
            "/apps/": {"function": self._apps, "methods": ["GET"]},
            "/apps/<path:filename>": {"function": self._apps, "methods": ["GET"]},
            "/stats/": {"function": self._stats, "methods": ["GET"]},
        }

        @app.route("/lib.js", methods=["GET"])
        def lib_js():
            return self._sillytavern("lib.js")

        @app.route("/script.js", methods=["GET"])
        def script_js():
            return self._sillytavern("script.js")

        @app.route("/lib/<path:filename>", methods=["GET"])
        def lib_files(filename):
            return self._sillytavern(f"lib/{filename}")

        @app.route("/scripts/<path:filename>", methods=["GET"])
        def script_files(filename):
            return self._sillytavern(f"scripts/{filename}")

    def _index(self, filename="home"):
        return render(filename)

    def _stats(self):
        return render("stats")

    def _chat(self, filename=""):
        filename = f"chat/{filename}" if filename else "chat/index"
        return render(filename)

    def _private(self, filename=""):
        filename = f"private/{filename}" if filename else "private/index"
        return render(filename)

    def _dist(self, name: str):
        return render(f"dist/{name}")

    def _apps(self, filename: str = "index.html"):
        return render(f"apps/{filename}")

    def _sillytavern(self, filename: str = "index.html"):
        SILLYTAVERN_URL = "https://raw.githubusercontent.com/SillyTavern/SillyTavern/refs/heads/release/"
        return render(f"public/{filename}", SILLYTAVERN_URL)

    def _playground(self, filename: str = "index.html"):
        PLAYGROUND_URL = (
            "https://raw.githubusercontent.com/gpt4free/playground/refs/heads/main/"
        )
        if not filename or filename.endswith("/"):
            filename = "index.html"
        filename += "" if "." in filename else ".html"
        # Serve from local ./playground directory if present
        local_dir = os.path.abspath("./playground")
        local_path = os.path.normpath(os.path.join(local_dir, filename))
        if local_path.startswith(local_dir + os.sep) and os.path.isfile(local_path):
            return send_from_directory(
                os.path.dirname(local_path),
                os.path.basename(local_path),
                max_age=31536000,
            )
        # Use cache dir
        cache_dir = os.path.join(get_cookies_dir(), ".playground_cache")
        safe_path = os.path.normpath(os.path.join(cache_dir, filename))
        if not safe_path.startswith(cache_dir + os.sep) and safe_path != cache_dir:
            return redirect("/playground/")
        # Serve from cache if present
        if os.path.isfile(safe_path):
            return send_from_directory(
                os.path.dirname(safe_path),
                os.path.basename(safe_path),
                max_age=31536000,
            )
        # Download and cache from GitHub
        os.makedirs(os.path.dirname(safe_path), exist_ok=True)
        try:
            response = requests.get(f"{PLAYGROUND_URL}{filename}", timeout=10)
            response.raise_for_status()
        except requests.exceptions.SSLError:
            try:
                response = requests.get(
                    f"{PLAYGROUND_URL}{filename}", timeout=10, verify=False
                )
                response.raise_for_status()
            except requests.RequestException:
                pass
        except requests.RequestException:
            pass

        if "response" in locals() and response.status_code == 200:
            with open(safe_path, "wb") as f:
                f.write(response.content)
            return send_from_directory(
                os.path.dirname(safe_path),
                os.path.basename(safe_path),
                max_age=31536000,
            )
        # SPA fallback: serve index.html for unknown sub-paths
        index_path = os.path.join(cache_dir, "index.html")
        if os.path.isfile(index_path):
            return send_from_directory(cache_dir, "index.html")
        local_index = os.path.join(local_dir, "index.html")
        if os.path.isfile(local_index):
            return send_from_directory(local_dir, "index.html")
        return redirect("https://gpt4free.github.io/playground")