Files
2025-04-13 16:49:20 +02:00

140 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import FastAPI, Request, Body
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
import requests, random, logging, json
from logging.handlers import RotatingFileHandler
from fastapi.responses import HTMLResponse
from pathlib import Path
log_path = "/var/log/games/WordCraze.log"
logger = logging.getLogger("WordCraze")
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(log_path, maxBytes=1_000_000, backupCount=3)
formatter = logging.Formatter('%(asctime)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
app = FastAPI()
app.mount("/static", StaticFiles(directory="static", html=True), name="static")
@app.get("/", response_class=HTMLResponse)
async def root():
return Path("static/index.html").read_text(encoding="utf-8")
class GameState:
def __init__(self, word: str):
self.word = word
self.guessed = ["_"] * len(word)
self.attempts = 8
self.status = "playing"
self.correct_total = 0
state = None
def getWordsFromOllama():
try:
url = "https://at1.dynproxy.net/api/chat/completions"
headers = {"Authorization": "Bearer sk-d0e3a491b19c435a975b234969298cd0"}
body = {
"model": "gemma3:1b",
"messages": [
{
"role": "system",
"content": "Gib ein zufälliges Thema an (z.B. Tiere, Essen, Möbel) und dann ein Array mit 5 Wörtern dieses Themas mit 3 bis 6 Buchstaben im JSON-Format. Nur das Array, keine Einleitung."
},
{
"role": "user",
"content": "Einfaches Thema und 5 passende Wörter mit 36 Buchstaben im JSON-Array."
}
],
"stream": False
}
r = requests.post(url, json=body, headers=headers, timeout=5)
r.raise_for_status()
content = r.json().get("choices", [{}])[0].get("message", {}).get("content", "[]")
clean = content.strip()
if clean.startswith("```json"):
clean = clean.removeprefix("```json").strip()
if clean.endswith("```"):
clean = clean.removesuffix("```").strip()
logger.info(f"Ollama-Rohantwort:\n{clean}")
return json.loads(clean)
except Exception as e:
logger.warning(f"Fallback Ollama getWordsFromOllama(): {e}")
return []
@app.post("/api/start")
def start_game():
global state
words = getWordsFromOllama()
if not words:
return JSONResponse({"error": "No words found"}, status_code=500)
word = random.choice(words)
state = GameState(word)
try:
desc_req = requests.post(
"https://at1.dynproxy.net/api/chat/completions",
headers={"Authorization": "Bearer sk-d0e3a491b19c435a975b234969298cd0"},
json={
"model": "gemma3:1b",
"messages": [
{"role": "user", "content": f"Nenne ein Oberthema (wie Möbel, Tiere, Sport), zu dem das Wort {word} gehört. Antworte nur mit einem allgemeinen Wort. Nenne niemals das Wort selbst."}
],
"stream": False
},
timeout=5
)
desc_req.raise_for_status()
desc = desc_req.json().get("choices", [{}])[0].get("message", {}).get("content", "(unbekannt)").strip()
except Exception as e:
logger.warning(f"Beschreibung für '{word}' fehlgeschlagen: {e}")
desc = "(unbekannt)"
logger.info(f"Zufallsstart mit Wort: {word}")
return {"word": state.guessed, "attempts": state.attempts, "source": "ollama", "topic": desc}
@app.post("/api/guess/{letter}")
def guess_letter(letter: str):
global state
if not state or state.status != "playing":
return {"status": "not_started"}
letter = letter.lower()
correct_this_turn = 0
for i, char in enumerate(state.word.lower()):
if char == letter and state.guessed[i] == "_":
state.guessed[i] = state.word[i]
correct_this_turn += 1
state.correct_total += correct_this_turn
if "_" not in state.guessed:
state.status = "won"
elif correct_this_turn == 0:
state.attempts -= 1
if state.attempts <= 0:
state.status = "lost"
response = {
"word": state.guessed,
"attempts": state.attempts,
"correct_this_turn": correct_this_turn,
"total_correct": state.correct_total,
"status": state.status
}
if state.status != "playing":
response["target"] = state.word
return response
@app.post("/api/moderate")
async def moderate(request: Request):
data = await request.json()
text = data.get("text", "")
bad = any(w in text.lower() for w in ["shit", "fuck", "spam"])
logger.info(f"Moderation checked: '{text}'{'REJECTED' if bad else 'OK'}")
return {"is_ok": not bad}
@app.get("/api/ping")
def ping():
return {"status": "ok"}