Game-Gallery / app-backup.py
ginipick's picture
Update app-backup.py
38c180a verified
raw
history blame
9 kB
import os, re, time, json, datetime, requests, gradio as gr
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Vercel API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TOKEN = os.getenv("SVR_TOKEN")
TEAM = os.getenv("VERCEL_TEAM_ID")
if not TOKEN:
raise EnvironmentError("SVR_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
API = "https://api.vercel.com"
HEAD = {"Authorization": f"Bearer {TOKEN}"}
print(f"API ํ† ํฐ: {TOKEN[:4]}... (๋งˆ์Šคํ‚น๋จ)")
print(f"ํŒ€ ID: {TEAM if TEAM else '์—†์Œ'}")
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
BEST_FILE, PER_PAGE = "best_games.json", 48
def _init_best():
if not os.path.exists(BEST_FILE):
json.dump([], open(BEST_FILE, "w"))
def _load_best():
try:
data = json.load(open(BEST_FILE))
for it in data:
if "ts" not in it:
it["ts"] = int(it.get("timestamp", time.time()))
return data
except Exception as e:
print(f"BEST ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜: {e}")
return []
def fetch_all(limit=50):
"""
Vercel API v10/projects๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
"""
try:
# API ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
params = {"limit": limit}
if TEAM:
params["teamId"] = TEAM
print(f"Vercel API ํ˜ธ์ถœ (ํ”„๋กœ์ ํŠธ): {API}/v10/projects (params={params})")
resp = requests.get(f"{API}/v10/projects",
headers=HEAD, params=params, timeout=30)
print(f"API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {resp.status_code}")
if resp.status_code != 200:
print(f"API ์‘๋‹ต ์˜ค๋ฅ˜: {resp.status_code}, {resp.text[:200] if hasattr(resp, 'text') else ''}")
return []
data = resp.json()
if "projects" not in data:
print(f"API ์‘๋‹ต์— projects ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค: {str(data)[:200]}...")
return []
projects = data.get("projects", [])
print(f"{len(projects)}๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค")
# ๊ฐ ํ”„๋กœ์ ํŠธ์˜ ์ตœ์‹  ๋ฐฐํฌ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
games = []
for project in projects:
project_id = project.get("id")
project_name = project.get("name", "(์ œ๋ชฉ ์—†์Œ)")
# ํ”„๋กœ์ ํŠธ๋ณ„ ์ตœ์‹  ๋ฐฐํฌ ๊ฐ€์ ธ์˜ค๊ธฐ (projectId ํ•„ํ„ฐ ์ถ”๊ฐ€)
try:
deploy_params = {
"limit": 1,
"projectId": project_id, # ์ค‘์š”: ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์˜ ๋ฐฐํฌ๋งŒ ํ•„ํ„ฐ๋ง
"state": "READY"
}
if TEAM:
deploy_params["teamId"] = TEAM
print(f"ํ”„๋กœ์ ํŠธ {project_id} ({project_name}) ๋ฐฐํฌ ์กฐํšŒ ์ค‘...")
deploy_resp = requests.get(
f"{API}/v6/deployments",
headers=HEAD,
params=deploy_params,
timeout=30
)
if deploy_resp.status_code == 200:
deploy_data = deploy_resp.json()
deployments = deploy_data.get("deployments", [])
if deployments:
deployment = deployments[0]
url = deployment.get("url", "")
if url:
games.append({
"title": project_name,
"url": f"https://{url}",
"ts": int(deployment.get("created", time.time() * 1000) / 1000),
"projectId": project_id
})
print(f"ํ”„๋กœ์ ํŠธ {project_name}์˜ ๋ฐฐํฌ URL: https://{url}")
else:
print(f"ํ”„๋กœ์ ํŠธ {project_name}์— ๋ฐฐํฌ๋œ ๋ฒ„์ „์ด ์—†์Šต๋‹ˆ๋‹ค.")
except Exception as e:
print(f"ํ”„๋กœ์ ํŠธ {project_id}์˜ ๋ฐฐํฌ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ: {e}")
continue
print(f"์ด {len(games)}๊ฐœ์˜ ์œ ํšจํ•œ ๋ฐฐํฌ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค")
return sorted(games, key=lambda x: x["ts"], reverse=True)
except Exception as e:
print(f"Vercel API ์˜ค๋ฅ˜: {str(e)}")
import traceback
traceback.print_exc()
return []
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. ํŽ˜์ด์ง€๋„ค์ด์…˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def page(lst, pg):
s=(pg-1)*PER_PAGE; e=s+PER_PAGE
total=(len(lst)+PER_PAGE-1)//PER_PAGE
return lst[s:e], total
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. HTML 3-์—ด ๊ทธ๋ฆฌ๋“œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def html(cards, pg, total):
if not cards:
return "<div style='text-align:center;padding:70px;color:#555;'>ํ‘œ์‹œํ•  ๋ฐฐํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>"
css=r"""
<style>
body{margin:0;font-family:Poppins,sans-serif;background:linear-gradient(135deg,#C5E8FF 0%,#FFD6E0 100%);}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px 24px;margin:0 20px 60px;}
@media(max-width:1024px){.grid{grid-template-columns:repeat(2,1fr);} }
@media(max-width:640px){ .grid{grid-template-columns:1fr;} }
.card{background:#fff;border-radius:18px;overflow:hidden;box-shadow:0 10px 25px rgba(0,0,0,.08);transition:.3s}
.card:hover{transform:translateY(-6px);box-shadow:0 16px 40px rgba(0,0,0,.12)}
.hdr{padding:20px 24px;background:rgba(255,255,255,.75);backdrop-filter:blur(8px);border-bottom:1px solid #eee;}
.ttl{margin:0;font-size:1.15rem;font-weight:700;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.date{margin-top:4px;font-size:.85rem;color:#777;}
.frame{position:relative;width:100%;padding-top:60%;overflow:hidden;}
.frame iframe{position:absolute;top:0;left:0;width:142.857%;height:142.857%;
transform:scale(.7);transform-origin:top left;border:0;}
.foot{padding:14px 24px;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);text-align:right;}
.link{font-size:.85rem;font-weight:600;color:#4a6dd8;text-decoration:none;}
.cnt{text-align:center;font-size:.85rem;color:#555;margin:10px 0 40px;}
</style>"""
h=css+"<div class='grid'>"
for c in cards:
date=datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d")
h+=f"""
<div class='card'>
<div class='hdr'><p class='ttl'>{c['title']}</p><p class='date'>{date}</p></div>
<div class='frame'><iframe src="{c['url']}" loading="lazy"
allow="accelerometer; camera; encrypted-media; gyroscope;"></iframe></div>
<div class='foot'><a class='link' href="{c['url']}" target="_blank">์›๋ณธโ†—</a></div>
</div>"""
h+="</div><p class='cnt'>Page "+str(pg)+" / "+str(total)+"</p>"
return h
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. Gradio Blocks UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def build():
_init_best()
with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;}") as demo:
gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>๐ŸŽฎ Vibe Game Craft</h1>")
with gr.Row():
b_new = gr.Button("NEW", size="sm")
b_best = gr.Button("BEST", size="sm")
b_prev = gr.Button("โฌ…๏ธ Prev", size="sm")
b_next = gr.Button("Next โžก๏ธ", size="sm")
b_ref = gr.Button("๐Ÿ”„ Reload", size="sm")
tab = gr.State("new"); np = gr.State(1); bp = gr.State(1); out = gr.HTML()
def show_new(p=1): d,t=page(fetch_all(),p); return html(d,p,t),"new",p
def show_best(p=1): d,t=page(_load_best(),p); return html(d,p,t),"best",p
def prev(t,n,b):
if t=="new": n=max(1,n-1); h,_,_=show_new(n); return h,n,b
b=max(1,b-1); h,_,_=show_best(b); return h,n,b
def nxt(t,n,b):
if t=="new":
maxp=(len(fetch_all())+PER_PAGE-1)//PER_PAGE
n=min(maxp,n+1); h,_,_=show_new(n); return h,n,b
maxp=(len(_load_best())+PER_PAGE-1)//PER_PAGE
b=min(maxp,b+1); h,_,_=show_best(b); return h,n,b
b_new.click(show_new, outputs=[out,tab,np])
b_best.click(show_best,outputs=[out,tab,bp])
b_prev.click(prev, inputs=[tab,np,bp], outputs=[out,np,bp])
b_next.click(nxt, inputs=[tab,np,bp], outputs=[out,np,bp])
b_ref.click(lambda t,n,b: show_new(n)[0] if t=="new" else show_best(b)[0],
inputs=[tab,np,bp], outputs=[out])
demo.load(show_new, outputs=[out,tab,np])
return demo
app = build()
if __name__ == "__main__":
app.launch()