ginipick commited on
Commit
10c3f69
ยท
verified ยท
1 Parent(s): 2e9406d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -69
app.py CHANGED
@@ -1,67 +1,69 @@
1
 
2
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3
- import os, time, json, datetime, requests, gradio as gr
4
 
5
- # 1. Vercel API ์„ค์ •
6
- TOKEN = os.getenv("SVR_TOKEN") # ํ•„์ˆ˜
7
- TEAM = os.getenv("VERCEL_TEAM_ID") # ํŒ€ ๊ณ„์ •์ด๋ฉด ์ง€์ •
8
  if not TOKEN:
9
  raise EnvironmentError("SVR_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
10
  API = "https://api.vercel.com"
11
  HEAD = {"Authorization": f"Bearer {TOKEN}"}
12
 
13
- # 2. BEST ํƒญ ๋ฐ์ดํ„ฐ
14
- BEST_FILE = "best_games.json"
15
- PER_PAGE = 48
16
  def _init_best():
17
  if not os.path.exists(BEST_FILE):
18
  json.dump([], open(BEST_FILE, "w"))
19
  def _load_best():
20
  try:
21
- return json.load(open(BEST_FILE))
 
 
 
 
22
  except Exception:
23
  return []
24
 
25
- # 3. ๋ชจ๋“  ๋ฐฐํฌ ๊ฐ€์ ธ์˜ค๊ธฐ (v6)
 
 
26
  def fetch_all(limit=200):
 
27
  try:
28
  params = {"limit": limit}
29
- if TEAM:
30
- params["teamId"] = TEAM
31
- r = requests.get(f"{API}/v6/deployments",
32
- headers=HEAD, params=params, timeout=30)
33
  r.raise_for_status()
34
 
35
- out = []
36
  for d in r.json().get("deployments", []):
37
  if d.get("state") != "READY":
38
  continue
39
- created_ms = d.get("created", time.time()*1000)
40
- url_host = d.get("url", "")
41
- url_full = url_host if url_host.startswith("http") else f"https://{url_host}"
 
42
  out.append({
43
  "title": d.get("name", "(์ œ๋ชฉ ์—†์Œ)"),
44
  "url" : url_full,
45
- "ts" : int(created_ms / 1000)
46
  })
47
  return sorted(out, key=lambda x: x["ts"], reverse=True)
48
  except Exception as e:
49
  print("Vercel API ์˜ค๋ฅ˜:", e)
50
  return []
51
 
52
- # 4. ํŽ˜์ด์ง€๋„ค์ด์…˜
53
  def page(lst, pg):
54
- s = (pg-1)*PER_PAGE
55
- e = s + PER_PAGE
56
- total = (len(lst)+PER_PAGE-1)//PER_PAGE
57
  return lst[s:e], total
58
 
59
- # 5. HTML (3-์—ด ๊ทธ๋ฆฌ๋“œ, ๋ฐ˜์‘ํ˜•)
60
  def html(cards, pg, total):
61
  if not cards:
62
  return "<div style='text-align:center;padding:70px;color:#555;'>ํ‘œ์‹œํ•  ๋ฐฐํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>"
63
-
64
- css = r"""
65
  <style>
66
  body{margin:0;font-family:Poppins,sans-serif;background:linear-gradient(135deg,#C5E8FF 0%,#FFD6E0 100%);}
67
  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px 24px;margin:0 20px 60px;}
@@ -78,27 +80,25 @@ def html(cards, pg, total):
78
  .foot{padding:14px 24px;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);text-align:right;}
79
  .link{font-size:.85rem;font-weight:600;color:#4a6dd8;text-decoration:none;}
80
  .cnt{text-align:center;font-size:.85rem;color:#555;margin:10px 0 40px;}
81
- </style>
82
- """
83
- h = css + "<div class='grid'>"
84
  for c in cards:
85
- date = datetime.datetime.fromtimestamp(c["ts"]).strftime("%Y-%m-%d")
86
- h += f"""
87
  <div class='card'>
88
  <div class='hdr'><p class='ttl'>{c['title']}</p><p class='date'>{date}</p></div>
89
  <div class='frame'><iframe src="{c['url']}" loading="lazy"
90
  allow="accelerometer; camera; encrypted-media; gyroscope;"></iframe></div>
91
  <div class='foot'><a class='link' href="{c['url']}" target="_blank">์›๋ณธโ†—</a></div>
92
  </div>"""
93
- h += "</div><p class='cnt'>Page "+str(pg)+" / "+str(total)+"</p>"
94
  return h
95
 
96
- # 6. Gradio UI
97
  def build():
98
  _init_best()
99
  with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;}") as demo:
100
  gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>๐ŸŽฎ Vibe Game Craft</h1>")
101
-
102
  with gr.Row():
103
  b_new = gr.Button("NEW", size="sm")
104
  b_best = gr.Button("BEST", size="sm")
@@ -106,48 +106,31 @@ def build():
106
  b_next = gr.Button("Next โžก๏ธ", size="sm")
107
  b_ref = gr.Button("๐Ÿ”„ Reload", size="sm")
108
 
109
- tab = gr.State("new") # "new" / "best"
110
- np = gr.State(1) # NEW ํŽ˜์ด์ง€
111
- bp = gr.State(1) # BEST ํŽ˜์ด์ง€
112
- out = gr.HTML()
113
-
114
- # NEW / BEST ๋ Œ๋”
115
- def show_new(p=1):
116
- data, tot = page(fetch_all(), p)
117
- return html(data, p, tot), "new", p
118
- def show_best(p=1):
119
- data, tot = page(_load_best(), p)
120
- return html(data, p, tot), "best", p
121
-
122
- # Prev / Next
123
- def prev(t, n, b):
124
- if t=="new":
125
- n=max(1,n-1); h,_,_ = show_new(n); return h, n, b
126
- b=max(1,b-1); h,_,_ = show_best(b); return h, n, b
127
- def nxt(t, n, b):
128
  if t=="new":
129
  maxp=(len(fetch_all())+PER_PAGE-1)//PER_PAGE
130
- n=min(maxp,n+1); h,_,_ = show_new(n); return h, n, b
131
  maxp=(len(_load_best())+PER_PAGE-1)//PER_PAGE
132
- b=min(maxp,b+1); h,_,_ = show_best(b); return h, n, b
133
-
134
- # Reload
135
- def reload(t, n, b):
136
- return show_new(n)[0] if t=="new" else show_best(b)[0]
137
-
138
- # ๋ฒ„ํŠผ ์—ฐ๊ฒฐ
139
- b_new.click(show_new, outputs=[out, tab, np])
140
- b_best.click(show_best, outputs=[out, tab, bp])
141
- b_prev.click(prev, inputs=[tab, np, bp], outputs=[out, np, bp])
142
- b_next.click(nxt, inputs=[tab, np, bp], outputs=[out, np, bp])
143
- b_ref.click(reload, inputs=[tab, np, bp], outputs=[out])
144
 
145
- # ์ดˆ๊ธฐ ๋กœ๋“œ โ€” NEW 1ํŽ˜์ด์ง€
146
- demo.load(show_new, outputs=[out, tab, np])
 
 
 
 
147
 
 
148
  return demo
149
 
150
- # 7. ์‹คํ–‰
151
  app = build()
152
 
153
  if __name__ == "__main__":
 
1
 
2
+ import os, re, time, json, datetime, requests, gradio as gr
 
3
 
4
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Vercel API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
5
+ TOKEN = os.getenv("SVR_TOKEN")
6
+ TEAM = os.getenv("VERCEL_TEAM_ID")
7
  if not TOKEN:
8
  raise EnvironmentError("SVR_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
9
  API = "https://api.vercel.com"
10
  HEAD = {"Authorization": f"Bearer {TOKEN}"}
11
 
12
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
13
+ BEST_FILE, PER_PAGE = "best_games.json", 48
 
14
  def _init_best():
15
  if not os.path.exists(BEST_FILE):
16
  json.dump([], open(BEST_FILE, "w"))
17
  def _load_best():
18
  try:
19
+ data = json.load(open(BEST_FILE))
20
+ for it in data:
21
+ if "ts" not in it:
22
+ it["ts"] = int(it.get("timestamp", time.time()))
23
+ return data
24
  except Exception:
25
  return []
26
 
27
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. NEW: 6๊ธ€์ž vercel.app ๋ฐฐํฌ โ”€โ”€โ”€โ”€โ”€
28
+ PAT = re.compile(r"^[a-z0-9]{6}\.vercel\.app$", re.I)
29
+
30
  def fetch_all(limit=200):
31
+ """v6/deployments โ†’ READY ์ƒํƒœ ์ค‘ url์ด 6๊ธ€์ž vercel.app ์ธ ๊ฒƒ๋งŒ"""
32
  try:
33
  params = {"limit": limit}
34
+ if TEAM: params["teamId"] = TEAM
35
+ r = requests.get(f"{API}/v6/deployments", headers=HEAD, params=params, timeout=30)
 
 
36
  r.raise_for_status()
37
 
38
+ out=[]
39
  for d in r.json().get("deployments", []):
40
  if d.get("state") != "READY":
41
  continue
42
+ host = d.get("url", "")
43
+ if not PAT.match(host):
44
+ continue # 6๊ธ€์ž vercel.app ๊ทœ์น™ ๋ถˆ์ผ์น˜ โ†’ ์ œ์™ธ
45
+ url_full = f"https://{host}"
46
  out.append({
47
  "title": d.get("name", "(์ œ๋ชฉ ์—†์Œ)"),
48
  "url" : url_full,
49
+ "ts" : int(d.get("created", time.time()*1000) / 1000)
50
  })
51
  return sorted(out, key=lambda x: x["ts"], reverse=True)
52
  except Exception as e:
53
  print("Vercel API ์˜ค๋ฅ˜:", e)
54
  return []
55
 
56
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. ํŽ˜์ด์ง€๋„ค์ด์…˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
57
  def page(lst, pg):
58
+ s=(pg-1)*PER_PAGE; e=s+PER_PAGE
59
+ total=(len(lst)+PER_PAGE-1)//PER_PAGE
 
60
  return lst[s:e], total
61
 
62
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. HTML 3-์—ด ๊ทธ๋ฆฌ๋“œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
63
  def html(cards, pg, total):
64
  if not cards:
65
  return "<div style='text-align:center;padding:70px;color:#555;'>ํ‘œ์‹œํ•  ๋ฐฐํฌ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>"
66
+ css=r"""
 
67
  <style>
68
  body{margin:0;font-family:Poppins,sans-serif;background:linear-gradient(135deg,#C5E8FF 0%,#FFD6E0 100%);}
69
  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px 24px;margin:0 20px 60px;}
 
80
  .foot{padding:14px 24px;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);text-align:right;}
81
  .link{font-size:.85rem;font-weight:600;color:#4a6dd8;text-decoration:none;}
82
  .cnt{text-align:center;font-size:.85rem;color:#555;margin:10px 0 40px;}
83
+ </style>"""
84
+ h=css+"<div class='grid'>"
 
85
  for c in cards:
86
+ date=datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d")
87
+ h+=f"""
88
  <div class='card'>
89
  <div class='hdr'><p class='ttl'>{c['title']}</p><p class='date'>{date}</p></div>
90
  <div class='frame'><iframe src="{c['url']}" loading="lazy"
91
  allow="accelerometer; camera; encrypted-media; gyroscope;"></iframe></div>
92
  <div class='foot'><a class='link' href="{c['url']}" target="_blank">์›๋ณธโ†—</a></div>
93
  </div>"""
94
+ h+="</div><p class='cnt'>Page "+str(pg)+" / "+str(total)+"</p>"
95
  return h
96
 
97
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. Gradio Blocks UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
98
  def build():
99
  _init_best()
100
  with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;}") as demo:
101
  gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>๐ŸŽฎ Vibe Game Craft</h1>")
 
102
  with gr.Row():
103
  b_new = gr.Button("NEW", size="sm")
104
  b_best = gr.Button("BEST", size="sm")
 
106
  b_next = gr.Button("Next โžก๏ธ", size="sm")
107
  b_ref = gr.Button("๐Ÿ”„ Reload", size="sm")
108
 
109
+ tab = gr.State("new"); np = gr.State(1); bp = gr.State(1); out = gr.HTML()
110
+
111
+ def show_new(p=1): d,t=page(fetch_all(),p); return html(d,p,t),"new",p
112
+ def show_best(p=1): d,t=page(_load_best(),p); return html(d,p,t),"best",p
113
+
114
+ def prev(t,n,b):
115
+ if t=="new": n=max(1,n-1); h,_,_=show_new(n); return h,n,b
116
+ b=max(1,b-1); h,_,_=show_best(b); return h,n,b
117
+ def nxt(t,n,b):
 
 
 
 
 
 
 
 
 
 
118
  if t=="new":
119
  maxp=(len(fetch_all())+PER_PAGE-1)//PER_PAGE
120
+ n=min(maxp,n+1); h,_,_=show_new(n); return h,n,b
121
  maxp=(len(_load_best())+PER_PAGE-1)//PER_PAGE
122
+ b=min(maxp,b+1); h,_,_=show_best(b); return h,n,b
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ b_new.click(show_new, outputs=[out,tab,np])
125
+ b_best.click(show_best,outputs=[out,tab,bp])
126
+ b_prev.click(prev, inputs=[tab,np,bp], outputs=[out,np,bp])
127
+ b_next.click(nxt, inputs=[tab,np,bp], outputs=[out,np,bp])
128
+ b_ref.click(lambda t,n,b: show_new(n)[0] if t=="new" else show_best(b)[0],
129
+ inputs=[tab,np,bp], outputs=[out])
130
 
131
+ demo.load(show_new, outputs=[out,tab,np])
132
  return demo
133
 
 
134
  app = build()
135
 
136
  if __name__ == "__main__":