Spaces:
Running
Running
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() |