ginipick commited on
Commit
d68f6ae
ยท
verified ยท
1 Parent(s): 0dc8807

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -57
app.py CHANGED
@@ -3,24 +3,14 @@ import os, re, time, json, datetime, requests, gradio as gr
3
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Vercel API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4
  TOKEN = os.getenv("SVR_TOKEN")
5
  TEAM = os.getenv("VERCEL_TEAM_ID")
6
- TEAM_SLUG = os.getenv("VERCEL_TEAM_SLUG") # ํŒ€ ์Šฌ๋Ÿฌ๊ทธ๋„ ์ถ”๊ฐ€
7
-
8
- # ํŒ€ ID ๋˜๋Š” ์Šฌ๋Ÿฌ๊ทธ ํ™•์ธ
9
- if not TEAM and TEAM_SLUG:
10
- print(f"ํŒ€ ID ๋Œ€์‹  ํŒ€ ์Šฌ๋Ÿฌ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: {TEAM_SLUG}")
11
- elif not TEAM:
12
- # ์‹ค์ œ ํŒ€ ID๋กœ ๊ต์ฒดํ•˜์„ธ์š”
13
- TEAM = "team_YOUR_ACTUAL_TEAM_ID_HERE"
14
- print(f"ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— ํŒ€ ID๊ฐ€ ์—†์–ด์„œ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: {TEAM}")
15
 
16
  if not TOKEN:
17
  raise EnvironmentError("SVR_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
18
-
19
  API = "https://api.vercel.com"
20
  HEAD = {"Authorization": f"Bearer {TOKEN}"}
21
 
22
- print(f"์‚ฌ์šฉ ์ค‘์ธ ํŒ€ ์ •๋ณด: ID={TEAM if TEAM else '์—†์Œ'}, Slug={TEAM_SLUG if TEAM_SLUG else '์—†์Œ'}")
23
-
24
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
25
  BEST_FILE, PER_PAGE = "best_games.json", 48
26
  def _init_best():
@@ -50,12 +40,7 @@ def fetch_all(limit=200):
50
  print("์˜ค๋ฅ˜: API ํ† ํฐ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
51
  return []
52
 
53
- # API ๋ฌธ์„œ ๊ธฐ๋ฐ˜์œผ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
54
- params = {
55
- "limit": limit,
56
- # ์ƒํƒœ๊ฐ€ "READY"์ธ ๋ฐฐํฌ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ ํ•„ํ„ฐ๋ง
57
- "state": "READY"
58
- }
59
 
60
  # ํŒ€ ID ๋˜๋Š” ์Šฌ๋Ÿฌ๊ทธ ์ฒ˜๋ฆฌ
61
  if TEAM:
@@ -89,20 +74,11 @@ def fetch_all(limit=200):
89
 
90
  print(f"{len(data['deployments'])}๊ฐœ์˜ ๋ฐฐํฌ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค")
91
 
92
- # ๋””๋ฒ„๊น…: ์ฒซ ๋ฒˆ์งธ ์‘๋‹ต ํ˜•์‹ ํ™•์ธ
93
- if data["deployments"]:
94
- first = data["deployments"][0]
95
- print(f"\n์ฒซ ๋ฒˆ์งธ ๋ฐฐํฌ URL: {first.get('url')}")
96
- print(f"์ฒซ ๋ฒˆ์งธ ๋ฐฐํฌ Alias: {first.get('alias', [])}\n")
97
- print(f"์ฒซ ๋ฒˆ์งธ ๋ฐฐํฌ ์ƒํƒœ: {first.get('state')}\n")
98
-
99
  # ํŒจํ„ด ์™„ํ™”: vercel.app์œผ๋กœ ๋๋‚˜๋Š” ๋ชจ๋“  URL ํ—ˆ์šฉ
100
  domain_pat = re.compile(r"\.vercel\.app$", re.I)
101
 
102
  games = []
103
  for d in data.get("deployments", []):
104
- # ๋ฌธ์„œ์—์„œ๋Š” state ํ•„ํ„ฐ๋ง์„ URL ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ–ˆ์ง€๋งŒ,
105
- # ์ถ”๊ฐ€ ๊ฒ€์ฆ์„ ์œ„ํ•ด ์—ฌ๊ธฐ์„œ๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
106
  if d.get("state") != "READY":
107
  continue
108
 
@@ -120,26 +96,11 @@ def fetch_all(limit=200):
120
  break
121
 
122
  if matched_url:
123
- # ์‹œ๊ฐ„ ํ˜•์‹ ๋ณ€ํ™˜ ๊ฐœ์„ 
124
- created_time = d.get("created")
125
- if created_time:
126
- # milliseconds to seconds ๋ณ€ํ™˜
127
- try:
128
- ts = int(created_time / 1000)
129
- except:
130
- ts = int(time.time())
131
- else:
132
- ts = int(time.time())
133
-
134
  games.append({
135
  "title": d.get("name", "(์ œ๋ชฉ ์—†์Œ)"),
136
  "url": f"https://{matched_url}",
137
- "ts": ts,
138
- "meta": { # ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ
139
- "projectId": d.get("projectId"),
140
- "target": d.get("target"),
141
- "deploymentId": d.get("uid")
142
- }
143
  })
144
 
145
  print(f"vercel.app ๋„๋ฉ”์ธ ๋ฐฐํฌ {len(games)}๊ฐœ๋ฅผ ํ•„ํ„ฐ๋งํ–ˆ์Šต๋‹ˆ๋‹ค")
@@ -195,52 +156,72 @@ def html(cards, pg, total):
195
  // iframe ๋กœ๋“œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
196
  function handleIframeError(iframe) {
197
  const container = iframe.parentNode;
198
- const url = iframe.src;
199
  iframe.style.display = 'none';
200
  const errorDiv = document.createElement('div');
201
  errorDiv.className = 'frame-error';
 
 
 
 
 
 
 
 
 
202
  errorDiv.innerHTML = '<div class="frame-error-icon">๐Ÿ”’</div>' +
203
- '<div><strong>์ ‘๊ทผ์ด ์ œํ•œ๋œ ๋ฐฐํฌ์ž…๋‹ˆ๋‹ค</strong></div>' +
204
  '<div>๋ณด์•ˆ ์„ค์ •์œผ๋กœ ์ธํ•ด iframe์—์„œ ํ‘œ์‹œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค</div>' +
205
- '<button class="frame-error-btn" onclick="window.open(\'' + url + '\', \'_blank\')">์ƒˆ ํƒญ์—์„œ ์—ด๊ธฐ</button>';
 
206
  container.appendChild(errorDiv);
207
  }
208
 
209
  // iframe ๋กœ๋“œ ์„ฑ๊ณต ์ฒ˜๋ฆฌ
210
  function handleIframeLoad(iframe) {
211
  try {
212
- // CORS ์˜ค๋ฅ˜ ํƒ์ง€ ์‹œ๋„ - ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ iframe ์ ‘๊ทผ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ
213
  if(iframe.contentWindow.location.href) {
214
  iframe.style.opacity = 1;
215
  }
216
  } catch (e) {
217
  // CORS ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด handleIframeError ํ˜ธ์ถœ
218
- console.log("CORS ์˜ค๋ฅ˜ ๋˜๋Š” ์ ‘๊ทผ ๊ฑฐ๋ถ€๋จ:", e);
219
  handleIframeError(iframe);
220
  }
221
  }
222
 
223
  // iframe ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ
224
  document.addEventListener('DOMContentLoaded', function() {
 
 
 
 
 
 
225
  // ๋ชจ๋“  iframe์— ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
226
  const frames = document.querySelectorAll('iframe');
227
  frames.forEach(frame => {
228
- // ์˜ค๋ฅ˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
 
 
 
 
 
 
 
 
229
  frame.addEventListener('error', function() {
230
- console.log("iframe ๋กœ๋“œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ");
231
  handleIframeError(this);
232
  });
233
 
234
- // ๋กœ๋“œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
235
  frame.addEventListener('load', function() {
236
- console.log("iframe ๋กœ๋“œ๋จ:", this.src);
237
  handleIframeLoad(this);
238
  });
239
 
240
  // 5์ดˆ ํ›„์—๋„ ๋กœ๋“œ๋˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜๋กœ ๊ฐ„์ฃผ
241
  setTimeout(function() {
242
  if(frame.style.opacity !== '1') {
243
- console.log("iframe ๋กœ๋“œ ํƒ€์ž„์•„์›ƒ:", frame.src);
244
  handleIframeError(frame);
245
  }
246
  }, 5000);
@@ -248,19 +229,33 @@ def html(cards, pg, total):
248
  });
249
  </script>"""
250
 
251
- h=css+"<div class='grid'>"
 
 
 
 
 
 
 
252
  for c in cards:
253
  date=datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d")
 
 
 
 
 
 
 
254
  h+=f"""
255
  <div class='card'>
256
  <div class='hdr'><p class='ttl'>{c['title']}</p><p class='date'>{date}</p></div>
257
  <div class='frame'>
258
- <iframe src="{c['url']}" loading="lazy"
259
  referrerpolicy="no-referrer"
260
  sandbox="allow-scripts allow-same-origin allow-forms"
261
  allow="accelerometer; camera; encrypted-media; gyroscope;"></iframe>
262
  </div>
263
- <div class='foot'><a class='link' href="{c['url']}" target="_blank">์›๋ณธโ†—</a></div>
264
  </div>"""
265
  h+="</div><p class='cnt'>Page "+str(pg)+" / "+str(total)+"</p>"
266
  return h
 
3
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Vercel API โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4
  TOKEN = os.getenv("SVR_TOKEN")
5
  TEAM = os.getenv("VERCEL_TEAM_ID")
6
+ TEAM_SLUG = os.getenv("VERCEL_TEAM_SLUG")
7
+ BYPASS_SECRET = os.getenv("VERCEL_AUTOMATION_BYPASS_SECRET") # ์ž๋™ํ™” ์šฐํšŒ ๋น„๋ฐ€ํ‚ค ์ถ”๊ฐ€
 
 
 
 
 
 
 
8
 
9
  if not TOKEN:
10
  raise EnvironmentError("SVR_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
 
11
  API = "https://api.vercel.com"
12
  HEAD = {"Authorization": f"Bearer {TOKEN}"}
13
 
 
 
14
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. BEST ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
15
  BEST_FILE, PER_PAGE = "best_games.json", 48
16
  def _init_best():
 
40
  print("์˜ค๋ฅ˜: API ํ† ํฐ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
41
  return []
42
 
43
+ params = {"limit": limit}
 
 
 
 
 
44
 
45
  # ํŒ€ ID ๋˜๋Š” ์Šฌ๋Ÿฌ๊ทธ ์ฒ˜๋ฆฌ
46
  if TEAM:
 
74
 
75
  print(f"{len(data['deployments'])}๊ฐœ์˜ ๋ฐฐํฌ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค")
76
 
 
 
 
 
 
 
 
77
  # ํŒจํ„ด ์™„ํ™”: vercel.app์œผ๋กœ ๋๋‚˜๋Š” ๋ชจ๋“  URL ํ—ˆ์šฉ
78
  domain_pat = re.compile(r"\.vercel\.app$", re.I)
79
 
80
  games = []
81
  for d in data.get("deployments", []):
 
 
82
  if d.get("state") != "READY":
83
  continue
84
 
 
96
  break
97
 
98
  if matched_url:
 
 
 
 
 
 
 
 
 
 
 
99
  games.append({
100
  "title": d.get("name", "(์ œ๋ชฉ ์—†์Œ)"),
101
  "url": f"https://{matched_url}",
102
+ "ts": int(d.get("created", time.time() * 1000) / 1000),
103
+ "deploymentId": d.get("uid", "") # ๋ฐฐํฌ ID ์ €์žฅ
 
 
 
 
104
  })
105
 
106
  print(f"vercel.app ๋„๋ฉ”์ธ ๋ฐฐํฌ {len(games)}๊ฐœ๋ฅผ ํ•„ํ„ฐ๋งํ–ˆ์Šต๋‹ˆ๋‹ค")
 
156
  // iframe ๋กœ๋“œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
157
  function handleIframeError(iframe) {
158
  const container = iframe.parentNode;
159
+ const originalUrl = iframe.src;
160
  iframe.style.display = 'none';
161
  const errorDiv = document.createElement('div');
162
  errorDiv.className = 'frame-error';
163
+
164
+ // ์šฐํšŒ ๋น„๋ฐ€ํ‚ค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ URL์— ์ถ”๊ฐ€ (Protection Bypass)
165
+ let bypassUrl = originalUrl;
166
+ if (typeof BYPASS_SECRET !== 'undefined' && BYPASS_SECRET) {
167
+ // URL์— ์ด๋ฏธ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
168
+ const hasQuery = bypassUrl.includes('?');
169
+ bypassUrl = bypassUrl + (hasQuery ? '&' : '?') + 'bypass=' + encodeURIComponent(BYPASS_SECRET);
170
+ }
171
+
172
  errorDiv.innerHTML = '<div class="frame-error-icon">๐Ÿ”’</div>' +
173
+ '<div><strong>๋ณดํ˜ธ๋œ ๋ฐฐํฌ์ž…๋‹ˆ๋‹ค</strong></div>' +
174
  '<div>๋ณด์•ˆ ์„ค์ •์œผ๋กœ ์ธํ•ด iframe์—์„œ ํ‘œ์‹œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค</div>' +
175
+ '<button class="frame-error-btn" onclick="window.open(\'' + bypassUrl + '\', \'_blank\')">์ƒˆ ํƒญ์—์„œ ์—ด๊ธฐ</button>';
176
+
177
  container.appendChild(errorDiv);
178
  }
179
 
180
  // iframe ๋กœ๋“œ ์„ฑ๊ณต ์ฒ˜๋ฆฌ
181
  function handleIframeLoad(iframe) {
182
  try {
183
+ // CORS ์˜ค๋ฅ˜ ํƒ์ง€ ์‹œ๋„
184
  if(iframe.contentWindow.location.href) {
185
  iframe.style.opacity = 1;
186
  }
187
  } catch (e) {
188
  // CORS ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด handleIframeError ํ˜ธ์ถœ
189
+ console.log("CORS ์˜ค๋ฅ˜ ๋˜๋Š” ๋ณดํ˜ธ๋œ ๋ฐฐํฌ:", e);
190
  handleIframeError(iframe);
191
  }
192
  }
193
 
194
  // iframe ๋กœ๋“œ ์ƒํƒœ ํ™•์ธ
195
  document.addEventListener('DOMContentLoaded', function() {
196
+ // ์ž๋™ํ™” ์šฐํšŒ ๋น„๋ฐ€ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์„ค์ •
197
+ const bypassSecret = "BYPASS_SECRET_PLACEHOLDER";
198
+ if (bypassSecret && bypassSecret !== "BYPASS_SECRET_PLACEHOLDER") {
199
+ window.BYPASS_SECRET = bypassSecret;
200
+ }
201
+
202
  // ๋ชจ๋“  iframe์— ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
203
  const frames = document.querySelectorAll('iframe');
204
  frames.forEach(frame => {
205
+ // URL์— ์šฐํšŒ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€ (Protection Bypass)
206
+ if (window.BYPASS_SECRET) {
207
+ let url = frame.src;
208
+ const hasQuery = url.includes('?');
209
+ url = url + (hasQuery ? '&' : '?') + 'bypass=' + encodeURIComponent(window.BYPASS_SECRET);
210
+ frame.src = url;
211
+ }
212
+
213
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€
214
  frame.addEventListener('error', function() {
 
215
  handleIframeError(this);
216
  });
217
 
 
218
  frame.addEventListener('load', function() {
 
219
  handleIframeLoad(this);
220
  });
221
 
222
  // 5์ดˆ ํ›„์—๋„ ๋กœ๋“œ๋˜์ง€ ์•Š์œผ๋ฉด ์˜ค๋ฅ˜๋กœ ๊ฐ„์ฃผ
223
  setTimeout(function() {
224
  if(frame.style.opacity !== '1') {
 
225
  handleIframeError(frame);
226
  }
227
  }, 5000);
 
229
  });
230
  </script>"""
231
 
232
+ # ์šฐํšŒ ๋น„๋ฐ€ํ‚ค๊ฐ€ ์žˆ๋‹ค๋ฉด JavaScript์— ์ฃผ์ž…
233
+ bypass_script = ""
234
+ if BYPASS_SECRET:
235
+ bypass_script = f"<script>window.BYPASS_SECRET = '{BYPASS_SECRET}';</script>"
236
+ css = css.replace("BYPASS_SECRET_PLACEHOLDER", BYPASS_SECRET)
237
+
238
+ h = css + bypass_script + "<div class='grid'>"
239
+
240
  for c in cards:
241
  date=datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d")
242
+
243
+ # URL์— protection bypass ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
244
+ url = c['url']
245
+ if BYPASS_SECRET:
246
+ url_separator = '&' if '?' in url else '?'
247
+ url = f"{url}{url_separator}bypass={BYPASS_SECRET}"
248
+
249
  h+=f"""
250
  <div class='card'>
251
  <div class='hdr'><p class='ttl'>{c['title']}</p><p class='date'>{date}</p></div>
252
  <div class='frame'>
253
+ <iframe src="{url}" loading="lazy"
254
  referrerpolicy="no-referrer"
255
  sandbox="allow-scripts allow-same-origin allow-forms"
256
  allow="accelerometer; camera; encrypted-media; gyroscope;"></iframe>
257
  </div>
258
+ <div class='foot'><a class='link' href="{url}" target="_blank">์›๋ณธโ†—</a></div>
259
  </div>"""
260
  h+="</div><p class='cnt'>Page "+str(pg)+" / "+str(total)+"</p>"
261
  return h