import os, time, json, requests, datetime, gradio as gr # ──────────────────────────────────────────────────────────────────────────────── # 1. Vercel API 설정 ─ 환경변수에서 토큰 읽기 VERCEL_API_TOKEN = os.getenv("SVR_TOKEN") if not VERCEL_API_TOKEN: raise EnvironmentError("환경 변수 'SVR_TOKEN'이 설정되어 있지 않습니다!") VERCEL_API_URL = "https://api.vercel.com/v9" HEADERS = { "Authorization": f"Bearer {VERCEL_API_TOKEN}", "Content-Type": "application/json", } # ──────────────────────────────────────────────────────────────────────────────── # 2. 갤러리·페이지네이션 기본값 BEST_GAMES_FILE = "best_games.json" GAMES_PER_PAGE = 48 # ──────────────────────────────────────────────────────────────────────────────── # 3. BEST 탭용 샘플 게임 초기화 def initialize_best_games(): if not os.path.exists(BEST_GAMES_FILE): sample = [ { "title": "테트리스", "description": "클래식 테트리스 게임", "url": "https://tmkdop.vercel.app", "timestamp": time.time(), }, { "title": "스네이크", "description": "전통적인 스네이크 게임", "url": "https://tmkdop.vercel.app", "timestamp": time.time(), }, { "title": "팩맨", "description": "고전 아케이드 게임", "url": "https://tmkdop.vercel.app", "timestamp": time.time(), }, ] json.dump(sample, open(BEST_GAMES_FILE, "w")) def load_best_games(): try: return json.load(open(BEST_GAMES_FILE)) except Exception: return [] # ──────────────────────────────────────────────────────────────────────────────── # 4. 최신 Vercel 배포 → 게임 목록 def get_latest_deployments(): try: r = requests.get( f"{VERCEL_API_URL}/deployments", headers=HEADERS, params={"limit": 100}, timeout=30, ) r.raise_for_status() games = [] for d in r.json().get("deployments", []): if d.get("state") != "READY": continue ts = ( int(d["createdAt"] / 1000) if isinstance(d["createdAt"], (int, float)) else int(time.time()) ) games.append( { "title": d.get("name", "게임"), "description": f"배포된 게임: {d.get('name')}", "url": f"https://{d.get('url')}", "timestamp": ts, } ) return sorted(games, key=lambda x: x["timestamp"], reverse=True) except Exception as e: print("Vercel API 오류:", e) return [] # ──────────────────────────────────────────────────────────────────────────────── # 5. 페이지네이션 헬퍼 def paginate(lst, page): start = (page - 1) * GAMES_PER_PAGE end = start + GAMES_PER_PAGE total = (len(lst) + GAMES_PER_PAGE - 1) // GAMES_PER_PAGE return lst[start:end], total # ──────────────────────────────────────────────────────────────────────────────── # 6. 갤러리 HTML (사이트 미러링 스타일) def generate_gallery_html(games, page, total_pages, tab_name): if not games: return ( "
표시할 게임이 없습니다.
" ) # CSS (필요 최소만 인라인 - reference 스타일 재구성) css = """ """ html = css + "
" for g in games: date = datetime.datetime.fromtimestamp(g["timestamp"]).strftime("%Y-%m-%d") html += f"""

{g['title']}

{date}
""" # 페이지네이션 html += "
" if total_pages > 1: html += "
" if page > 1: html += ( f"" ) for p in range(max(1, page - 2), min(total_pages, page + 2) + 1): active = "active" if p == page else "" html += ( f"" ) if page < total_pages: html += ( f"" ) html += "
" # refreshGallery JS (탭 버튼 재사용) html += """ """ return html # ──────────────────────────────────────────────────────────────────────────────── # 7. Gradio UI def create_gallery_interface(): initialize_best_games() with gr.Blocks() as demo: gr.HTML("

🎮 Vibe Game Craft

") with gr.Row(): new_btn = gr.Button("NEW", elem_id="new-btn") best_btn = gr.Button("BEST", elem_id="best-btn") refresh = gr.Button("🔄 새로고침") tab_state = gr.State("new") page_state = gr.State(1) # NEW 탭 현재 페이지 page_state2 = gr.State(1) # BEST 탭 현재 페이지 gallery_out = gr.HTML() # 각 탭 렌더 함수 def render_new(page=1): games, total = paginate(get_latest_deployments(), page) return generate_gallery_html(games, page, total, "new"), "new", page def render_best(page=1): games, total = paginate(load_best_games(), page) return generate_gallery_html(games, page, total, "best"), "best", page new_btn.click(fn=render_new, outputs=[gallery_out, tab_state, page_state]) best_btn.click(fn=render_best, outputs=[gallery_out, tab_state, page_state2]) # 새로고침 def do_refresh(tab, p1, p2): if tab == "new": return render_new(p1)[0] return render_best(p2)[0] refresh.click(fn=do_refresh, inputs=[tab_state, page_state, page_state2], outputs=[gallery_out]) # 페이지네이션을 위한 JS 콜백용 숨은 함수 def set_page(tab, p): if tab == "new": return render_new(p) return render_best(p) # 초기 NEW demo.load(fn=render_new, outputs=[gallery_out, tab_state, page_state]) # JS 에서 호출될 수 있도록 함수 노출 demo.set_event_trigger("__setPage", set_page, [tab_state, page_state, page_state2], [gallery_out, tab_state, page_state, page_state2]) return demo # ──────────────────────────────────────────────────────────────────────────────── # 8. 실행 (Gradio Spaces 자동 감지를 위해 전역 app 변수 노출) app = create_gallery_interface() if __name__ == "__main__": app.launch()