from fastapi import FastAPI, UploadFile, File import shutil import uuid import json import os import asyncio from contextlib import asynccontextmanager from datetime import datetime, timedelta from services.time_service import get_local_time, update_time from services.discord_sync import sync async def widget_loop(): last_force_sync = None while True: now = datetime.now() print( "SYNC TICK", now.strftime("%H:%M:%S"), flush=True ) try: update_time() result = sync() if result: print( "Widget sync:", result.status_code, result.text, flush=True ) # force sync в полночь today = now.date() if now.hour == 0 and now.minute == 0: if last_force_sync != today: print( "MIDNIGHT FORCE SYNC", flush=True ) update_time() result = sync() print( "FORCE RESULT:", result.status_code if result else "FAILED", flush=True ) last_force_sync = today except Exception as e: print( "Widget error:", repr(e), flush=True ) await asyncio.sleep(60) @asynccontextmanager async def lifespan(app): print("AUTOSYNC STARTED", flush=True) asyncio.create_task( widget_loop() ) yield app = FastAPI( lifespan=lifespan ) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) PROFILE_FILE = os.path.join(BASE_DIR, "..", "config", "profile.json") HERO_DIR = os.path.join(BASE_DIR, "..", "assets", "heroes") os.makedirs(HERO_DIR, exist_ok=True) def load_profile(): try: with open(PROFILE_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: return {} def save_profile(data): with open(PROFILE_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) @app.get("/profile") def get_profile(): return load_profile() @app.post("/profile") def update_profile(data: dict): save_profile(data) return {"status": "ok"} @app.get("/time") def get_time(): return { "time": get_local_time() } @app.post("/hero/upload") async def upload_hero(file: UploadFile = File(...)): filename = f"{uuid.uuid4()}_{file.filename}" path = os.path.join(HERO_DIR, filename) with open(path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) return { "status": "ok", "hero": filename }