ginipick commited on
Commit
21582cd
·
verified ·
1 Parent(s): 9cea90a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -115
app.py CHANGED
@@ -27,7 +27,51 @@ def _load_best():
27
  print(f"BEST 데이터 로드 오류: {e}")
28
  return []
29
 
30
- # ───────────────────── 3. 배포 보호 설정 관리 ─────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def get_projects():
32
  """Vercel 프로젝트 목록을 가져옵니다."""
33
  try:
@@ -139,99 +183,6 @@ def bypass_all_protection():
139
  print(f"총 {len(projects)}개 중 {success_count}개 프로젝트의 보호 설정을 비활성화했습니다.")
140
  return success_count > 0
141
 
142
- # ───────────────────── 4. 배포 목록 가져오기 ─────────────────
143
- def fetch_all(limit=200, auto_bypass=True):
144
- """
145
- Vercel API v6/deployments를 사용하여 모든 준비된 배포를 가져오고,
146
- 필요에 따라 자동으로 보호 우회 설정을 적용합니다.
147
- """
148
- try:
149
- # 자동 보호 우회 적용 (선택 사항)
150
- if auto_bypass:
151
- print("모든 프로젝트에 대한 보호 설정 자동 우회를 시도합니다...")
152
- bypass_all_protection()
153
-
154
- # API 파라미터 설정
155
- params = {"limit": limit, "state": "READY"}
156
- if TEAM:
157
- params["teamId"] = TEAM
158
-
159
- print(f"Vercel API 호출 (배포): {API}/v6/deployments (params={params})")
160
-
161
- resp = requests.get(f"{API}/v6/deployments",
162
- headers=HEAD, params=params, timeout=30)
163
-
164
- if resp.status_code != 200:
165
- print(f"API 응답 오류: {resp.status_code}, {resp.text[:200] if hasattr(resp, 'text') else ''}")
166
- return []
167
-
168
- data = resp.json()
169
-
170
- if "deployments" not in data:
171
- print(f"API 응답에 deployments 필드가 없습니다: {str(data)[:200]}...")
172
- return []
173
-
174
- deployments = data.get("deployments", [])
175
- print(f"{len(deployments)}개의 배포를 찾았습니다")
176
-
177
- # 도메인 패턴: vercel.app으로 끝나는지 확인
178
- domain_pat = re.compile(r"\.vercel\.app$", re.I)
179
-
180
- # 중복 방지를 위한 세트 (이미 처리한 URL)
181
- processed_urls = set()
182
- processed_projects = set()
183
-
184
- games = []
185
- for deployment in deployments:
186
- # URL 확인
187
- url = deployment.get("url", "")
188
- project_id = deployment.get("projectId")
189
-
190
- # 이미 처리한 URL은 건너뜀
191
- if url in processed_urls:
192
- continue
193
-
194
- processed_urls.add(url)
195
-
196
- # vercel.app 도메인인지 확인
197
- if url and domain_pat.search(url):
198
- # 프로젝트별 보호 우회 설정 (각 프로젝트당 한 번만)
199
- if auto_bypass and project_id and project_id not in processed_projects:
200
- disable_protection(project_id)
201
- processed_projects.add(project_id)
202
-
203
- # 타임스탬프 처리
204
- created_time = deployment.get("created")
205
- if created_time:
206
- try:
207
- ts = int(created_time / 1000)
208
- except:
209
- ts = int(time.time())
210
- else:
211
- ts = int(time.time())
212
-
213
- # 이름에 projectId가 있으면 보기 좋게 변환
214
- name = deployment.get("name", "(제목 없음)")
215
- name = re.sub(r"-[a-f0-9]{9}$", "", name) # projectId 부분 제거
216
-
217
- games.append({
218
- "title": name,
219
- "url": f"https://{url}",
220
- "ts": ts,
221
- "projectId": project_id,
222
- "deploymentId": deployment.get("uid", "")
223
- })
224
- print(f"배포 추가: {name} - https://{url}")
225
-
226
- print(f"총 {len(games)}개의 유효한 배포를 필터링했습니다")
227
- return sorted(games, key=lambda x: x["ts"], reverse=True)
228
-
229
- except Exception as e:
230
- print(f"Vercel API 오류: {str(e)}")
231
- import traceback
232
- traceback.print_exc()
233
- return []
234
-
235
  # ───────────────────── 5. 페이지네이션 ───────────────────
236
  def page(lst, pg):
237
  s=(pg-1)*PER_PAGE; e=s+PER_PAGE
@@ -279,18 +230,44 @@ def build():
279
  _init_best()
280
  with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;}") as demo:
281
  gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>🎮 Vibe Game Craft</h1>")
 
 
 
 
 
 
 
 
282
  with gr.Row():
283
- b_new = gr.Button("NEW", size="sm")
284
  b_best = gr.Button("BEST", size="sm")
285
  b_prev = gr.Button("⬅️ Prev", size="sm")
286
  b_next = gr.Button("Next ➡️", size="sm")
287
  b_ref = gr.Button("🔄 Reload", size="sm")
288
  b_bypass = gr.Button("🔓 보호 해제", size="sm", variant="secondary")
289
 
290
- tab = gr.State("new"); np = gr.State(1); bp = gr.State(1); out = gr.HTML()
 
 
 
291
 
292
- def show_new(p=1): d,t=page(fetch_all(),p); return html(d,p,t),"new",p
293
- def show_best(p=1): d,t=page(_load_best(),p); return html(d,p,t),"best",p
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
  def run_bypass():
296
  if bypass_all_protection():
@@ -298,25 +275,27 @@ def build():
298
  else:
299
  return "❌ 일부 또는 모든 프로젝트의 보호 설정 비활성화에 실패했습니다."
300
 
301
- def prev(t,n,b):
302
- if t=="new": n=max(1,n-1); h,_,_=show_new(n); return h,n,b
303
- b=max(1,b-1); h,_,_=show_best(b); return h,n,b
304
- def nxt(t,n,b):
305
- if t=="new":
306
- maxp=(len(fetch_all())+PER_PAGE-1)//PER_PAGE
307
- n=min(maxp,n+1); h,_,_=show_new(n); return h,n,b
308
- maxp=(len(_load_best())+PER_PAGE-1)//PER_PAGE
309
- b=min(maxp,b+1); h,_,_=show_best(b); return h,n,b
 
310
 
311
- b_new.click(show_new, outputs=[out,tab,np])
312
- b_best.click(show_best,outputs=[out,tab,bp])
313
- b_prev.click(prev, inputs=[tab,np,bp], outputs=[out,np,bp])
314
- b_next.click(nxt, inputs=[tab,np,bp], outputs=[out,np,bp])
315
- b_ref.click(lambda t,n,b: show_new(n)[0] if t=="new" else show_best(b)[0],
316
- inputs=[tab,np,bp], outputs=[out])
317
- b_bypass.click(run_bypass, outputs=[out])
318
 
319
- demo.load(show_new, outputs=[out,tab,np])
 
320
  return demo
321
 
322
  app = build()
 
27
  print(f"BEST 데이터 로드 오류: {e}")
28
  return []
29
 
30
+ def _save_best(data):
31
+ try:
32
+ json.dump(data, open(BEST_FILE, "w"))
33
+ return True
34
+ except Exception as e:
35
+ print(f"BEST 데이터 저장 오류: {e}")
36
+ return False
37
+
38
+ # ───────────────────── 3. URL 추가 기능 ─────────────────────
39
+ def add_url_to_best(title, url):
40
+ """사용자가 제공한 URL을 BEST 목록에 추가합니다."""
41
+ try:
42
+ # 현재 BEST 데이터 로드
43
+ data = _load_best()
44
+
45
+ # URL이 이미 존재하는지 확인
46
+ for item in data:
47
+ if item.get("url") == url:
48
+ print(f"URL이 이미 존재합니다: {url}")
49
+ return False
50
+
51
+ # 새 항목 추가
52
+ new_item = {
53
+ "title": title,
54
+ "url": url,
55
+ "ts": int(time.time()),
56
+ "projectId": "", # 사용자가 직접 추가하므로 projectId 없음
57
+ "deploymentId": "" # 사용자가 직접 추가하므로 deploymentId 없음
58
+ }
59
+
60
+ data.append(new_item)
61
+
62
+ # 시간순으로 정렬
63
+ data = sorted(data, key=lambda x: x["ts"], reverse=True)
64
+
65
+ # 저장
66
+ if _save_best(data):
67
+ print(f"URL이 성공적으로 추가되었습니다: {url}")
68
+ return True
69
+ return False
70
+ except Exception as e:
71
+ print(f"URL 추가 오류: {str(e)}")
72
+ return False
73
+
74
+ # ───────────────────── 4. 배포 보호 설정 관리 ─────────────────
75
  def get_projects():
76
  """Vercel 프로젝트 목록을 가져옵니다."""
77
  try:
 
183
  print(f"총 {len(projects)}개 중 {success_count}개 프로젝트의 보호 설정을 비활성화했습니다.")
184
  return success_count > 0
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  # ───────────────────── 5. 페이지네이션 ───────────────────
187
  def page(lst, pg):
188
  s=(pg-1)*PER_PAGE; e=s+PER_PAGE
 
230
  _init_best()
231
  with gr.Blocks(title="Vibe Game Craft", css="body{overflow-x:hidden;}") as demo:
232
  gr.Markdown("<h1 style='text-align:center;padding:32px 0 0;color:#333;'>🎮 Vibe Game Craft</h1>")
233
+
234
+ # URL 추가 인터페이스
235
+ with gr.Row():
236
+ title_input = gr.Textbox(label="제목", placeholder="게임 제목을 입력하세요")
237
+ url_input = gr.Textbox(label="URL", placeholder="https://...")
238
+ add_btn = gr.Button("URL 추가", variant="primary")
239
+
240
+ # 페이지 네비게이션
241
  with gr.Row():
 
242
  b_best = gr.Button("BEST", size="sm")
243
  b_prev = gr.Button("⬅️ Prev", size="sm")
244
  b_next = gr.Button("Next ➡️", size="sm")
245
  b_ref = gr.Button("🔄 Reload", size="sm")
246
  b_bypass = gr.Button("🔓 보호 해제", size="sm", variant="secondary")
247
 
248
+ # 상태 출력
249
+ bp = gr.State(1)
250
+ out = gr.HTML()
251
+ status_msg = gr.Textbox(label="상태 메시지", interactive=False)
252
 
253
+ def show_best(p=1):
254
+ d, t = page(_load_best(), p)
255
+ return html(d, p, t), p
256
+
257
+ def add_url():
258
+ title = title_input.value
259
+ url = url_input.value
260
+
261
+ if not title or not url:
262
+ return "❌ 제목과 URL을 모두 입력해주세요."
263
+
264
+ if not url.startswith("http"):
265
+ url = "https://" + url
266
+
267
+ if add_url_to_best(title, url):
268
+ return f"✅ URL이 성공적으로 추가되었습니다: {url}"
269
+ else:
270
+ return f"❌ URL 추가에 실패했습니다: {url}"
271
 
272
  def run_bypass():
273
  if bypass_all_protection():
 
275
  else:
276
  return "❌ 일부 또는 모든 프로젝트의 보호 설정 비활성화에 실패했습니다."
277
 
278
+ def prev(b):
279
+ b = max(1, b-1)
280
+ h, _ = show_best(b)
281
+ return h, b
282
+
283
+ def nxt(b):
284
+ maxp = (len(_load_best()) + PER_PAGE - 1) // PER_PAGE
285
+ b = min(maxp, b+1)
286
+ h, _ = show_best(b)
287
+ return h, b
288
 
289
+ # 이벤트 연결
290
+ add_btn.click(add_url, outputs=[status_msg])
291
+ b_best.click(show_best, outputs=[out, bp])
292
+ b_prev.click(prev, inputs=[bp], outputs=[out, bp])
293
+ b_next.click(nxt, inputs=[bp], outputs=[out, bp])
294
+ b_ref.click(lambda b: show_best(b)[0], inputs=[bp], outputs=[out])
295
+ b_bypass.click(run_bypass, outputs=[status_msg])
296
 
297
+ # 초기 로드
298
+ demo.load(show_best, outputs=[out, bp])
299
  return demo
300
 
301
  app = build()