Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,29 +6,35 @@ from selenium import webdriver
|
|
| 6 |
from selenium.common.exceptions import WebDriverException
|
| 7 |
from PIL import Image
|
| 8 |
from io import BytesIO
|
|
|
|
| 9 |
|
| 10 |
def take_screenshot(url):
|
| 11 |
"""웹사이트 스크린샷 촬영 함수"""
|
|
|
|
|
|
|
|
|
|
| 12 |
options = webdriver.ChromeOptions()
|
| 13 |
options.add_argument('--headless')
|
| 14 |
options.add_argument('--no-sandbox')
|
| 15 |
options.add_argument('--disable-dev-shm-usage')
|
|
|
|
| 16 |
|
| 17 |
try:
|
| 18 |
driver = webdriver.Chrome(options=options)
|
| 19 |
-
driver.set_window_size(1080, 720) # 스크린샷 크기 설정
|
| 20 |
driver.get(url)
|
| 21 |
driver.implicitly_wait(10)
|
| 22 |
screenshot = driver.get_screenshot_as_png()
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
| 24 |
except WebDriverException as e:
|
| 25 |
print(f"스크린샷 촬영 실패: {str(e)}")
|
| 26 |
-
return
|
| 27 |
finally:
|
| 28 |
-
if driver:
|
| 29 |
driver.quit()
|
| 30 |
|
| 31 |
-
|
| 32 |
USERNAME = "openfree"
|
| 33 |
|
| 34 |
def format_timestamp(timestamp):
|
|
@@ -279,7 +285,7 @@ def get_vercel_deployments():
|
|
| 279 |
print(f"Error fetching Vercel deployments: {str(e)}")
|
| 280 |
return []
|
| 281 |
|
| 282 |
-
def get_vercel_card(deployment, index):
|
| 283 |
"""Vercel 배포 카드 HTML 생성 함수"""
|
| 284 |
raw_url = deployment.get('url', '')
|
| 285 |
|
|
@@ -295,61 +301,27 @@ def get_vercel_card(deployment, index):
|
|
| 295 |
|
| 296 |
# 카드 ID 생성
|
| 297 |
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
# 스크린샷 이미지 가져오기
|
| 301 |
-
try:
|
| 302 |
-
screenshot = take_screenshot(url)
|
| 303 |
-
screenshot_html = f"""
|
| 304 |
-
<div style="width: 100%; height: 200px; overflow: hidden; border-radius: 10px; margin-bottom: 15px;">
|
| 305 |
-
<img src="data:image/png;base64,{screenshot}"
|
| 306 |
-
style="width: 100%; height: 100%; object-fit: cover;"
|
| 307 |
-
alt="{name} 스크린샷"/>
|
| 308 |
-
</div>
|
| 309 |
-
"""
|
| 310 |
-
except Exception as e:
|
| 311 |
-
print(f"스크린샷 처리 오류: {str(e)}")
|
| 312 |
-
screenshot_html = "" # 오류 시 스크린샷 영역 생략
|
| 313 |
-
|
| 314 |
-
# 나머지 카드 스타일링 코드는 기존과 동일...
|
| 315 |
-
return f"""
|
| 316 |
-
<div id="{card_id}" class="vercel-card"
|
| 317 |
-
style='border: none;
|
| 318 |
-
padding: 25px;
|
| 319 |
-
margin: 15px;
|
| 320 |
-
border-radius: 20px;
|
| 321 |
-
background-color: {get_pastel_color(index)};
|
| 322 |
-
box-shadow: 0 4px 15px rgba(0,0,0,0.1);'>
|
| 323 |
-
{screenshot_html}
|
| 324 |
-
<h3>{name}</h3>
|
| 325 |
-
<div style='margin: 15px 0;'>
|
| 326 |
-
<p>상태: {state}</p>
|
| 327 |
-
<p>생성일: {created}</p>
|
| 328 |
-
<p>URL: <a href="{url}" target="_blank">{url}</a></p>
|
| 329 |
-
</div>
|
| 330 |
-
</div>
|
| 331 |
-
"""
|
| 332 |
-
|
| 333 |
-
# Hugging Face 스페이스 URL인 경우 직접 사용
|
| 334 |
-
if 'huggingface.co' in url:
|
| 335 |
-
final_url = url
|
| 336 |
-
else:
|
| 337 |
-
final_url = f"https://{url}" if not url.startswith('http') else url
|
| 338 |
-
|
| 339 |
-
created = format_timestamp(deployment.get('created'))
|
| 340 |
-
name = deployment.get('name', 'Unnamed Project')
|
| 341 |
-
state = deployment.get('state', 'N/A')
|
| 342 |
-
|
| 343 |
-
# 고유 ID 생성 (카드 식별용)
|
| 344 |
-
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
|
| 345 |
|
| 346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
|
| 348 |
-
bg_color = get_pastel_color(index + 20)
|
| 349 |
tech_emojis = ['⚡', '🚀', '🌟', '✨', '💫', '🔥', '🌈', '🎯', '🎨', '🔮']
|
| 350 |
random_emojis = random.sample(tech_emojis, 3)
|
| 351 |
|
| 352 |
-
|
| 353 |
return f"""
|
| 354 |
<div id="{card_id}" class="vercel-card"
|
| 355 |
data-likes="0"
|
|
@@ -364,7 +336,7 @@ def get_vercel_card(deployment, index):
|
|
| 364 |
overflow: hidden;'
|
| 365 |
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
|
| 366 |
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
|
| 367 |
-
|
| 368 |
<h3 style='color: #2d2d2d;
|
| 369 |
margin: 0 0 20px 0;
|
| 370 |
font-size: 1.4em;
|
|
@@ -372,7 +344,7 @@ def get_vercel_card(deployment, index):
|
|
| 372 |
align-items: center;
|
| 373 |
gap: 10px;'>
|
| 374 |
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
|
| 375 |
-
<a href='{
|
| 376 |
style='text-decoration: none; color: #2d2d2d;'>
|
| 377 |
{name}
|
| 378 |
</a>
|
|
@@ -388,7 +360,7 @@ def get_vercel_card(deployment, index):
|
|
| 388 |
<strong>Created:</strong> 📅 {created}
|
| 389 |
</p>
|
| 390 |
<p style='margin: 8px 0;'>
|
| 391 |
-
<strong>URL:</strong> 🔗
|
| 392 |
</p>
|
| 393 |
</div>
|
| 394 |
<div style='margin-top: 20px; display: flex; justify-content: space-between; align-items: center;'>
|
|
@@ -399,7 +371,7 @@ def get_vercel_card(deployment, index):
|
|
| 399 |
</button>
|
| 400 |
<span class="like-count" style="font-size: 1.2em; color: #666;">0</span>
|
| 401 |
</div>
|
| 402 |
-
<a href='{
|
| 403 |
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
|
| 404 |
color: white;
|
| 405 |
padding: 10px 20px;
|
|
@@ -634,9 +606,8 @@ def get_user_spaces():
|
|
| 634 |
# TOP_BEST_URLS 항목 수
|
| 635 |
top_best_count = len(TOP_BEST_URLS)
|
| 636 |
|
| 637 |
-
# Vercel API를 통한 실제 배포 수
|
| 638 |
vercel_deployments = get_vercel_deployments()
|
| 639 |
-
print(f"Debug - Vercel API response: {vercel_deployments}") # 디버깅 로그
|
| 640 |
actual_vercel_count = len(vercel_deployments) if vercel_deployments else 0
|
| 641 |
|
| 642 |
html_content = f"""
|
|
@@ -659,8 +630,11 @@ def get_user_spaces():
|
|
| 659 |
<!-- Top Best -->
|
| 660 |
<h3 style='color: #333; margin: 20px 0;'>🏆 Top Best</h3>
|
| 661 |
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
|
| 662 |
-
{"".join(get_vercel_card(
|
| 663 |
-
|
|
|
|
|
|
|
|
|
|
| 664 |
</div>
|
| 665 |
|
| 666 |
<!-- Vercel Deployments -->
|
|
@@ -731,7 +705,6 @@ def get_user_spaces():
|
|
| 731 |
</script>
|
| 732 |
"""
|
| 733 |
|
| 734 |
-
|
| 735 |
return html_content
|
| 736 |
|
| 737 |
except Exception as e:
|
|
@@ -743,15 +716,14 @@ def get_user_spaces():
|
|
| 743 |
<p>Please try again later.</p>
|
| 744 |
</div>
|
| 745 |
"""
|
|
|
|
| 746 |
|
| 747 |
# Creating the Gradio interface
|
| 748 |
demo = gr.Blocks()
|
| 749 |
|
| 750 |
with demo:
|
| 751 |
-
html_output = gr.HTML(value=get_user_spaces())
|
| 752 |
|
| 753 |
if __name__ == "__main__":
|
| 754 |
-
demo
|
| 755 |
-
|
| 756 |
-
gr.HTML(value=get_user_spaces())
|
| 757 |
-
demo.launch()
|
|
|
|
| 6 |
from selenium.common.exceptions import WebDriverException
|
| 7 |
from PIL import Image
|
| 8 |
from io import BytesIO
|
| 9 |
+
import base64
|
| 10 |
|
| 11 |
def take_screenshot(url):
|
| 12 |
"""웹사이트 스크린샷 촬영 함수"""
|
| 13 |
+
if not url.startswith('http'):
|
| 14 |
+
url = f"https://{url}"
|
| 15 |
+
|
| 16 |
options = webdriver.ChromeOptions()
|
| 17 |
options.add_argument('--headless')
|
| 18 |
options.add_argument('--no-sandbox')
|
| 19 |
options.add_argument('--disable-dev-shm-usage')
|
| 20 |
+
options.add_argument('--window-size=1080,720')
|
| 21 |
|
| 22 |
try:
|
| 23 |
driver = webdriver.Chrome(options=options)
|
|
|
|
| 24 |
driver.get(url)
|
| 25 |
driver.implicitly_wait(10)
|
| 26 |
screenshot = driver.get_screenshot_as_png()
|
| 27 |
+
img = Image.open(BytesIO(screenshot))
|
| 28 |
+
buffered = BytesIO()
|
| 29 |
+
img.save(buffered, format="PNG")
|
| 30 |
+
return base64.b64encode(buffered.getvalue()).decode()
|
| 31 |
except WebDriverException as e:
|
| 32 |
print(f"스크린샷 촬영 실패: {str(e)}")
|
| 33 |
+
return None
|
| 34 |
finally:
|
| 35 |
+
if 'driver' in locals():
|
| 36 |
driver.quit()
|
| 37 |
|
|
|
|
| 38 |
USERNAME = "openfree"
|
| 39 |
|
| 40 |
def format_timestamp(timestamp):
|
|
|
|
| 285 |
print(f"Error fetching Vercel deployments: {str(e)}")
|
| 286 |
return []
|
| 287 |
|
| 288 |
+
def get_vercel_card(deployment, index, is_top_best=False):
|
| 289 |
"""Vercel 배포 카드 HTML 생성 함수"""
|
| 290 |
raw_url = deployment.get('url', '')
|
| 291 |
|
|
|
|
| 301 |
|
| 302 |
# 카드 ID 생성
|
| 303 |
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
+
# Top Best 항목일 경우만 스크린샷 추가
|
| 306 |
+
screenshot_html = ""
|
| 307 |
+
if is_top_best:
|
| 308 |
+
try:
|
| 309 |
+
screenshot_base64 = take_screenshot(raw_url)
|
| 310 |
+
if screenshot_base64:
|
| 311 |
+
screenshot_html = f"""
|
| 312 |
+
<div style="width: 100%; height: 200px; overflow: hidden; border-radius: 10px; margin-bottom: 15px;">
|
| 313 |
+
<img src="data:image/png;base64,{screenshot_base64}"
|
| 314 |
+
style="width: 100%; height: 100%; object-fit: cover;"
|
| 315 |
+
alt="{name} 스크린샷"/>
|
| 316 |
+
</div>
|
| 317 |
+
"""
|
| 318 |
+
except Exception as e:
|
| 319 |
+
print(f"스크린샷 처리 오류: {str(e)}")
|
| 320 |
|
| 321 |
+
bg_color = get_pastel_color(index + (20 if not is_top_best else 0))
|
| 322 |
tech_emojis = ['⚡', '🚀', '🌟', '✨', '💫', '🔥', '🌈', '🎯', '🎨', '🔮']
|
| 323 |
random_emojis = random.sample(tech_emojis, 3)
|
| 324 |
|
|
|
|
| 325 |
return f"""
|
| 326 |
<div id="{card_id}" class="vercel-card"
|
| 327 |
data-likes="0"
|
|
|
|
| 336 |
overflow: hidden;'
|
| 337 |
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
|
| 338 |
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
|
| 339 |
+
{screenshot_html}
|
| 340 |
<h3 style='color: #2d2d2d;
|
| 341 |
margin: 0 0 20px 0;
|
| 342 |
font-size: 1.4em;
|
|
|
|
| 344 |
align-items: center;
|
| 345 |
gap: 10px;'>
|
| 346 |
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
|
| 347 |
+
<a href='{url}' target='_blank'
|
| 348 |
style='text-decoration: none; color: #2d2d2d;'>
|
| 349 |
{name}
|
| 350 |
</a>
|
|
|
|
| 360 |
<strong>Created:</strong> 📅 {created}
|
| 361 |
</p>
|
| 362 |
<p style='margin: 8px 0;'>
|
| 363 |
+
<strong>URL:</strong> 🔗 {url}
|
| 364 |
</p>
|
| 365 |
</div>
|
| 366 |
<div style='margin-top: 20px; display: flex; justify-content: space-between; align-items: center;'>
|
|
|
|
| 371 |
</button>
|
| 372 |
<span class="like-count" style="font-size: 1.2em; color: #666;">0</span>
|
| 373 |
</div>
|
| 374 |
+
<a href='{url}' target='_blank'
|
| 375 |
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
|
| 376 |
color: white;
|
| 377 |
padding: 10px 20px;
|
|
|
|
| 606 |
# TOP_BEST_URLS 항목 수
|
| 607 |
top_best_count = len(TOP_BEST_URLS)
|
| 608 |
|
| 609 |
+
# Vercel API를 통한 실제 배포 수
|
| 610 |
vercel_deployments = get_vercel_deployments()
|
|
|
|
| 611 |
actual_vercel_count = len(vercel_deployments) if vercel_deployments else 0
|
| 612 |
|
| 613 |
html_content = f"""
|
|
|
|
| 630 |
<!-- Top Best -->
|
| 631 |
<h3 style='color: #333; margin: 20px 0;'>🏆 Top Best</h3>
|
| 632 |
<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;'>
|
| 633 |
+
{"".join(get_vercel_card(
|
| 634 |
+
{"url": url["url"], "created": url["created"], "name": url["name"], "state": url["state"]},
|
| 635 |
+
idx,
|
| 636 |
+
is_top_best=True
|
| 637 |
+
) for idx, url in enumerate(TOP_BEST_URLS))}
|
| 638 |
</div>
|
| 639 |
|
| 640 |
<!-- Vercel Deployments -->
|
|
|
|
| 705 |
</script>
|
| 706 |
"""
|
| 707 |
|
|
|
|
| 708 |
return html_content
|
| 709 |
|
| 710 |
except Exception as e:
|
|
|
|
| 716 |
<p>Please try again later.</p>
|
| 717 |
</div>
|
| 718 |
"""
|
| 719 |
+
|
| 720 |
|
| 721 |
# Creating the Gradio interface
|
| 722 |
demo = gr.Blocks()
|
| 723 |
|
| 724 |
with demo:
|
| 725 |
+
html_output = gr.HTML(value=get_user_spaces())
|
| 726 |
|
| 727 |
if __name__ == "__main__":
|
| 728 |
+
demo.launch()
|
| 729 |
+
|
|
|
|
|
|