imageeditingpro1 / server.py
Transfer Bot
Moved to Hugging Face automatically
5514a5d
Raw History Blame Contribute Delete
4.3 kB
import asyncio
import hmac
import os
import re
import threading
import time
from fastapi import FastAPI, Header, HTTPException
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
import builder
app = FastAPI(title="Lesson content server")
state = {"status": "idle", "log": [], "errors": {}, "finished": None, "voices": {}}
lock = threading.Lock()
def _log(msg):
state["log"].append(msg)
del state["log"][:-200]
def run_build():
if not lock.acquire(blocking=False):
return
try:
state.update(status="building", log=[], errors={})
_, errors = asyncio.run(builder.build_all(_log))
state.update(status="ready", errors=errors, voices=dict(builder.INFO), finished=time.strftime("%Y-%m-%d %H:%M:%S"))
except Exception as e: # noqa: BLE001
_log(f"خطای کلی: {e}")
state["status"] = "failed"
finally:
lock.release()
@app.on_event("startup")
def startup():
threading.Thread(target=run_build, daemon=True).start()
def check_password(p):
expected = os.getenv("REBUILD_PASSWORD", "")
if expected and not hmac.compare_digest(p or "", expected):
raise HTTPException(401, "رمز اشتباه است")
@app.get("/manifest.json")
def manifest():
f = builder.OUT / "manifest.json"
if not f.exists():
return JSONResponse({"status": state["status"]}, status_code=503)
return FileResponse(f, media_type="application/json", headers={"Cache-Control": "no-cache"})
@app.get("/packs/{name}")
def pack(name: str):
if not re.fullmatch(r"[A-Za-z0-9_.-]+\.zip", name):
raise HTTPException(400, "bad name")
f = builder.OUT / "packs" / name
if not f.exists():
raise HTTPException(404, "not found")
return FileResponse(f, media_type="application/zip")
@app.get("/status")
def status():
return state
@app.post("/rebuild")
def rebuild(x_password: str = Header(default="")):
check_password(x_password)
if state["status"] == "building":
return {"ok": False, "msg": "ساخت در حال انجام است"}
threading.Thread(target=run_build, daemon=True).start()
return {"ok": True}
PAGE = """<!doctype html><html lang="fa" dir="rtl"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>سرور درس‌ها</title>
<style>body{font-family:system-ui,Tahoma;max-width:720px;margin:24px auto;padding:0 16px;background:#f4f9f9;color:#10282b}
.card{background:#fff;border-radius:14px;padding:16px;margin:12px 0;box-shadow:0 1px 4px #0001}
button{background:#0b7f8b;color:#fff;border:0;border-radius:10px;padding:10px 18px;font-size:15px;cursor:pointer}
input{padding:9px;border-radius:8px;border:1px solid #9bb;font-size:15px}
pre{background:#10282b;color:#cfe;padding:12px;border-radius:10px;max-height:300px;overflow:auto;direction:ltr;text-align:left;white-space:pre-wrap}
.ok{color:#1e9e6a}.bad{color:#d9482b}</style>
<h2>سرور درس‌ها</h2>
<div class="card"><div>وضعیت: <b id="st">…</b></div><div id="fin"></div>
<p>آدرس فهرست: <code id="url" dir="ltr"></code></p>
<p><input id="pw" type="password" placeholder="رمز (اگر تعریف کرده‌اید)"> <button onclick="rb()">ساخت دوباره‌ی درس‌ها و صداها</button></p></div>
<div class="card"><b>گزارش</b><pre id="log"></pre><div id="err" class="bad"></div></div>
<script>
document.getElementById('url').textContent=location.origin+'/manifest.json';
async function load(){const s=await (await fetch('status')).json();
const m={idle:'آماده',building:'در حال ساخت…',ready:'آماده ✅',failed:'ناموفق ❌'};
st.textContent=m[s.status]||s.status;fin.textContent=(s.finished?('آخرین ساخت: '+s.finished):'')+(s.voices&&s.voices.words?(' | واژه‌ها: '+s.voices.words+' | جمله‌ها: '+s.voices.sentences):'');
log.textContent=s.log.join('\\n');err.textContent=Object.entries(s.errors).map(([k,v])=>k+': '+v).join('\\n');}
async function rb(){const r=await fetch('rebuild',{method:'POST',headers:{'X-Password':pw.value}});
if(!r.ok){alert('رمز اشتباه است');return}load()}
load();setInterval(load,3000);
</script></html>"""
@app.get("/", response_class=HTMLResponse)
def home():
return PAGE