Game-Gallery / app.py
ginipick's picture
Update app.py
293f472 verified
raw
history blame
12.1 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", 9
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 _save_best(data):
try:
json.dump(data, open(BEST_FILE, "w"))
return True
except Exception as e:
print(f"BEST ๋ฐ์ดํ„ฐ ์ €์žฅ ์˜ค๋ฅ˜: {e}")
return False
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. URL ์ถ”๊ฐ€ ๊ธฐ๋Šฅ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def add_url_to_best(title, url):
"""์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ URL์„ BEST ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
try:
# ํ˜„์žฌ BEST ๋ฐ์ดํ„ฐ ๋กœ๋“œ
data = _load_best()
# URL์ด ์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
for item in data:
if item.get("url") == url:
print(f"URL์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: {url}")
return False
# ์ƒˆ ํ•ญ๋ชฉ ์ถ”๊ฐ€
new_item = {
"title": title,
"url": url,
"ts": int(time.time()),
"projectId": "", # ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ projectId ์—†์Œ
"deploymentId": "" # ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ deploymentId ์—†์Œ
}
data.append(new_item)
# ์‹œ๊ฐ„์ˆœ์œผ๋กœ ์ •๋ ฌ
data = sorted(data, key=lambda x: x["ts"], reverse=True)
# ์ €์žฅ
if _save_best(data):
print(f"URL์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค: {url}")
return True
return False
except Exception as e:
print(f"URL ์ถ”๊ฐ€ ์˜ค๋ฅ˜: {str(e)}")
return False
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. ๋ฐฐํฌ ๋ณดํ˜ธ ์„ค์ • ๊ด€๋ฆฌ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def get_projects():
"""Vercel ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค."""
try:
params = {"limit": 100} # ์ตœ๋Œ€ 100๊ฐœ ํ”„๋กœ์ ํŠธ
if TEAM:
params["teamId"] = TEAM
print(f"ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...")
resp = requests.get(f"{API}/v10/projects", headers=HEAD, params=params)
if resp.status_code != 200:
print(f"ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ: {resp.status_code}")
return []
projects = resp.json().get("projects", [])
print(f"{len(projects)}๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.")
return projects
except Exception as e:
print(f"ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜: {e}")
return []
def disable_protection(project_id):
"""ํŠน์ • ํ”„๋กœ์ ํŠธ์˜ ๋ฐฐํฌ ๋ณดํ˜ธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค."""
try:
url = f"{API}/v9/projects/{project_id}/protection"
payload = {
"protection": {
"preview": {
"enabled": False
},
"production": {
"enabled": False
}
}
}
print(f"ํ”„๋กœ์ ํŠธ {project_id}์˜ ๋ณดํ˜ธ ์„ค์ • ๋น„ํ™œ์„ฑํ™” ์ค‘...")
resp = requests.put(url, headers=HEAD, json=payload)
if resp.status_code in [200, 201, 204]:
print(f"ํ”„๋กœ์ ํŠธ {project_id}์˜ ๋ณดํ˜ธ ์„ค์ •์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
return True
else:
print(f"๋ณดํ˜ธ ์„ค์ • ๋ณ€๊ฒฝ ์‹คํŒจ: {resp.status_code}, {resp.text[:200] if hasattr(resp, 'text') else ''}")
return False
except Exception as e:
print(f"๋ณดํ˜ธ ์„ค์ • ๋ณ€๊ฒฝ ์˜ค๋ฅ˜: {str(e)}")
return False
def add_exception_domain(project_id, domain):
"""ํŠน์ • ํ”„๋กœ์ ํŠธ์— ๋ณดํ˜ธ ์˜ˆ์™ธ ๋„๋ฉ”์ธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."""
try:
url = f"{API}/v9/projects/{project_id}/protection"
# ๋จผ์ € ํ˜„์žฌ ๋ณดํ˜ธ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
get_resp = requests.get(url, headers=HEAD)
if get_resp.status_code != 200:
print(f"๋ณดํ˜ธ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ: {get_resp.status_code}")
return False
current = get_resp.json()
# ์˜ˆ์™ธ ๋„๋ฉ”์ธ ๋ชฉ๋ก์— ์ถ”๊ฐ€
exceptions = current.get("protection", {}).get("preview", {}).get("exceptions", [])
if domain not in exceptions:
exceptions.append(domain)
# ์—…๋ฐ์ดํŠธ๋œ ์„ค์ • ์ ์šฉ
payload = {
"protection": {
"preview": {
"enabled": True,
"exceptions": exceptions
}
}
}
print(f"ํ”„๋กœ์ ํŠธ {project_id}์— ์˜ˆ์™ธ ๋„๋ฉ”์ธ {domain} ์ถ”๊ฐ€ ์ค‘...")
resp = requests.put(url, headers=HEAD, json=payload)
if resp.status_code in [200, 201, 204]:
print(f"ํ”„๋กœ์ ํŠธ {project_id}์— ์˜ˆ์™ธ ๋„๋ฉ”์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
return True
else:
print(f"์˜ˆ์™ธ ๋„๋ฉ”์ธ ์ถ”๊ฐ€ ์‹คํŒจ: {resp.status_code}, {resp.text[:200] if hasattr(resp, 'text') else ''}")
return False
except Exception as e:
print(f"์˜ˆ์™ธ ๋„๋ฉ”์ธ ์ถ”๊ฐ€ ์˜ค๋ฅ˜: {str(e)}")
return False
def bypass_all_protection():
"""๋ชจ๋“  ํ”„๋กœ์ ํŠธ์˜ ๋ฐฐํฌ ๋ณดํ˜ธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค."""
projects = get_projects()
if not projects:
print("ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
return False
success_count = 0
for project in projects:
project_id = project.get("id")
name = project.get("name", "์•Œ ์ˆ˜ ์—†์Œ")
if project_id:
print(f"ํ”„๋กœ์ ํŠธ ์ฒ˜๋ฆฌ ์ค‘: {name} ({project_id})")
if disable_protection(project_id):
success_count += 1
print(f"์ด {len(projects)}๊ฐœ ์ค‘ {success_count}๊ฐœ ํ”„๋กœ์ ํŠธ์˜ ๋ณดํ˜ธ ์„ค์ •์„ ๋น„ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.")
return success_count > 0
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. ํŽ˜์ด์ง€๋„ค์ด์…˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
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
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. 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 auto;max-width:1200px;padding:20px;}
@media(max-width:1024px){.grid{grid-template-columns:repeat(3,1fr);} }
@media(max-width:768px){.grid{grid-template-columns:repeat(2,1fr);} }
@media(max-width:500px){ .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 20px;}
</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
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 7. Gradio Blocks UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def build():
_init_best()
with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;} .gradio-container{margin:0 auto; max-width:1280px;}") as demo:
gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>๐ŸŽฎ Vibe Game Craft</h1>")
# URL ์ถ”๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค
with gr.Row():
title_input = gr.Textbox(label="์ œ๋ชฉ", placeholder="๊ฒŒ์ž„ ์ œ๋ชฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”")
url_input = gr.Textbox(label="URL", placeholder="https://...")
add_btn = gr.Button("URL ์ถ”๊ฐ€", variant="primary")
# ํŽ˜์ด์ง€ ๋„ค๋น„๊ฒŒ์ด์…˜
with gr.Row():
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")
b_bypass = gr.Button("๐Ÿ”“ ๋ณดํ˜ธ ํ•ด์ œ", size="sm", variant="secondary")
# ์ƒํƒœ ๋ฐ ์ถœ๋ ฅ
bp = gr.State(1)
out = gr.HTML()
status_msg = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€", interactive=False)
def show_best(p=1):
d, t = page(_load_best(), p)
return html(d, p, t), p
def prev(b):
b = max(1, b-1)
h, _ = show_best(b)
return h, b
def nxt(b):
maxp = (len(_load_best()) + PER_PAGE - 1) // PER_PAGE
b = min(maxp, b+1)
h, _ = show_best(b)
return h, b
# ํŽ˜์ด์ง€ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ (ํ•˜๋‹จ์— ๋ฐฐ์น˜)
with gr.Row(elem_classes="navigation-buttons"):
b_prev = gr.Button("โฌ…๏ธ ์ด์ „ ํŽ˜์ด์ง€", size="sm")
b_next = gr.Button("๋‹ค์Œ ํŽ˜์ด์ง€ โžก๏ธ", size="sm")
# ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
b_prev.click(prev, inputs=[bp], outputs=[out, bp])
b_next.click(nxt, inputs=[bp], outputs=[out, bp])
# CSS ์Šคํƒ€์ผ ์ถ”๊ฐ€
css = """
.navigation-buttons {
display: flex;
justify-content: center;
margin-bottom: 40px;
}
.navigation-buttons button {
margin: 0 10px;
min-width: 120px;
}
"""
demo.load(show_best, outputs=[out, bp])
return demo
app = build()
if __name__ == "__main__":
app.launch()