<filename>app.py
import os
import time
from concurrent.futures import ThreadPoolExecutor
from enum import Enum
from hashlib import sha256
from typing import List, Type

import genshinstats as gs
from cachetools import TTLCache
from fastapi import Depends, FastAPI, HTTPException, Path, Query, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI(
    title="genshinstats API",
    description="Api for genshin stats",
    contact={"name": "sadru", "url": "https://github.com/thesadru"},
)

security = HTTPBasic()

gs.set_cookie(ltuid=os.environ["GS_LTUID"], ltoken=os.environ["GS_LTOKEN"])
cache = TTLCache(1024, 3600)
gs.install_cache(cache)

user_passwords = {
    "sadru": "<PASSWORD>",
    "desertfox": "<PASSWORD>",
}

langs = gs.get_langs()
Lang: Type[str] = Enum("Lang", dict(zip(langs, langs)), type=str)


@app.on_event("startup")
async def startup_event():
    global startup_time
    startup_time = time.time()


@app.get("/", include_in_schema=False)
def index():
    return RedirectResponse(app.docs_url)


@app.get("/user/{uid}", tags=["stats"], summary="User Stats")
def user(uid: int = Path(..., description="user's game uid", example=710785423)):
    """User's stats, characters, exploration percentage and teapot info"""
    return gs.get_user_stats(uid)


@app.get("/abyss/{uid}", tags=["stats"], summary="Spiral Abyss")
def abyss(
    uid: int = Path(..., description="user's game uid", example=710785423),
    previous: bool = Query(False, description="Return the previous season's spiral abyss"),
):
    """User's Spiral Abyss runs during an entire season"""
    return gs.get_spiral_abyss(uid, previous)


@app.get("/characters/{uid}", tags=["stats"], summary="Characters")
def characters(
    uid: int = Path(..., description="user's game uid", example=710785423),
    name: str = Query(None, description="Character's name"),
    lang: Lang = Query("en-us", description="Language of the response"),
):
    """List of user's characters"""
    characters = gs.get_characters(uid, lang=lang)
    if name is None:
        return characters

    for char in characters:
        if char["name"].title() == name.title():
            return char
    else:
        raise HTTPException(status_code=418, detail=f"User doesn't have a character named {name!r}")


@app.get("/gacha", tags=["gacha"], summary="Current banners")
def gacha():
    """List of the information of all current gacha banners."""
    with open("gacha_banners.txt") as file:
        banners = file.read().splitlines()
    
    with ThreadPoolExecutor() as executor:
        return list(executor.map(gs.get_banner_details, banners))


@app.post("/gacha", tags=["gacha"])
def update_gacha(ids: List[str], credentials: HTTPBasicCredentials = Depends(security)):
    """Update the banner ids, requires the user to be authorized"""
    password = <PASSWORD>_<PASSWORD>.get(credentials.username)
    if sha256(credentials.password.encode()).hexdigest() != password:
        raise HTTPException(403, "You are not authorized")

    with open("gacha_banners.txt", "w") as file:
        file.write("\n".join(ids))

    return {"detail": "success"}


@app.get("/gacha/items", tags=["gacha"], summary="Gacha Items")
def gacha_items():
    """A list of all items that you can pull from the gacha excluding limited items."""
    return gs.get_gacha_items()


@app.get("/debug", include_in_schema=False)
def debug(request: Request, include_cache: bool = Query(False, alias="cache")):
    from importlib.metadata import version
    return {
        "type": request.scope["type"],
        "host": request.scope["server"][0],
        "port": request.scope["server"][1],
        "asgi": request.scope["asgi"],
        "uptime": time.time() - startup_time,
        "genshinstats_version": version('genshinstats'),
        "cache": {", ".join(map(str, k)): v for k, v in cache.items()} if include_cache else None,
    }


@app.exception_handler(gs.GenshinStatsException)
def handle_genshinstats(request: Request, exc: gs.GenshinStatsException):
    return JSONResponse(
        {"error": type(exc).__name__, "retcode": exc.retcode, "message": exc.msg, "orig_msg": exc.orig_msg},
        status_code=400,
    )

<filename>app.py
import os
import time
from concurrent.futures import ThreadPoolExecutor
from enum import Enum
from hashlib import sha256
from typing import List, Type

import genshinstats as gs
from cachetools import TTLCache
from fastapi import Depends, FastAPI, HTTPException, Path, Query, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI(
    title="genshinstats API",
    description="Api for genshin stats",
    contact={"name": "sadru", "url": "https://github.com/thesadru"},
)

security = HTTPBasic()

gs.set_cookie(ltuid=os.environ["GS_LTUID"], ltoken=os.environ["GS_LTOKEN"])
cache = TTLCache(1024, 3600)
gs.install_cache(cache)

user_passwords = {
    "sadru": "<PASSWORD>",
    "desertfox": "<PASSWORD>",
}

langs = gs.get_langs()
Lang: Type[str] = Enum("Lang", dict(zip(langs, langs)), type=str)


@app.on_event("startup")
async def startup_event():
    global startup_time
    startup_time = time.time()


@app.get("/", include_in_schema=False)
def index():
    return RedirectResponse(app.docs_url)


@app.get("/user/{uid}", tags=["stats"], summary="User Stats")
def user(uid: int = Path(..., description="user's game uid", example=710785423)):
    """User's stats, characters, exploration percentage and teapot info"""
    return gs.get_user_stats(uid)


@app.get("/abyss/{uid}", tags=["stats"], summary="Spiral Abyss")
def abyss(
    uid: int = Path(..., description="user's game uid", example=710785423),
    previous: bool = Query(False, description="Return the previous season's spiral abyss"),
):
    """User's Spiral Abyss runs during an entire season"""
    return gs.get_spiral_abyss(uid, previous)


@app.get("/characters/{uid}", tags=["stats"], summary="Characters")
def characters(
    uid: int = Path(..., description="user's game uid", example=710785423),
    name: str = Query(None, description="Character's name"),
    lang: Lang = Query("en-us", description="Language of the response"),
):
    """List of user's characters"""
    characters = gs.get_characters(uid, lang=lang)
    if name is None:
        return characters

    for char in characters:
        if char["name"].title() == name.title():
            return char
    else:
        raise HTTPException(status_code=418, detail=f"User doesn't have a character named {name!r}")


@app.get("/gacha", tags=["gacha"], summary="Current banners")
def gacha():
    """List of the information of all current gacha banners."""
    with open("gacha_banners.txt") as file:
        banners = file.read().splitlines()
    
    with ThreadPoolExecutor() as executor:
        return list(executor.map(gs.get_banner_details, banners))


@app.post("/gacha", tags=["gacha"])
def update_gacha(ids: List[str], credentials: HTTPBasicCredentials = Depends(security)):
    """Update the banner ids, requires the user to be authorized"""
    password = <PASSWORD>_<PASSWORD>.get(credentials.username)
    if sha256(credentials.password.encode()).hexdigest() != password:
        raise HTTPException(403, "You are not authorized")

    with open("gacha_banners.txt", "w") as file:
        file.write("\n".join(ids))

    return {"detail": "success"}


@app.get("/gacha/items", tags=["gacha"], summary="Gacha Items")
def gacha_items():
    """A list of all items that you can pull from the gacha excluding limited items."""
    return gs.get_gacha_items()


@app.get("/debug", include_in_schema=False)
def debug(request: Request, include_cache: bool = Query(False, alias="cache")):
    from importlib.metadata import version
    return {
        "type": request.scope["type"],
        "host": request.scope["server"][0],
        "port": request.scope["server"][1],
        "asgi": request.scope["asgi"],
        "uptime": time.time() - startup_time,
        "genshinstats_version": version('genshinstats'),
        "cache": {", ".join(map(str, k)): v for k, v in cache.items()} if include_cache else None,
    }


@app.exception_handler(gs.GenshinStatsException)
def handle_genshinstats(request: Request, exc: gs.GenshinStatsException):
    return JSONResponse(
        {"error": type(exc).__name__, "retcode": exc.retcode, "message": exc.msg, "orig_msg": exc.orig_msg},
        status_code=400,
    )

User's stats, characters, exploration percentage and teapot info User's Spiral Abyss runs during an entire season List of user's characters List of the information of all current gacha banners. Update the banner ids, requires the user to be authorized A list of all items that you can pull from the gacha excluding limited items.