trending-board / app-backup.py
openfree's picture
Update app-backup.py
5e82b1d verified
raw
history blame
31.6 kB
import requests
import gradio as gr
from datetime import datetime
import random
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException, TimeoutException
from PIL import Image
from io import BytesIO
import base64
import time
def take_screenshot(url):
"""์›น์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ ํ•จ์ˆ˜ (๋กœ๋”ฉ ๋Œ€๊ธฐ ์‹œ๊ฐ„ ์ถ”๊ฐ€)"""
if not url.startswith('http'):
url = f"https://{url}"
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--window-size=1080,720')
try:
driver = webdriver.Chrome(options=options)
driver.get(url)
# ๋ช…์‹œ์  ๋Œ€๊ธฐ: body ์š”์†Œ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ (์ตœ๋Œ€ 10์ดˆ)
try:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
except TimeoutException:
print(f"ํŽ˜์ด์ง€ ๋กœ๋”ฉ ํƒ€์ž„์•„์›ƒ: {url}")
# ์ถ”๊ฐ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„ (1์ดˆ)
time.sleep(1)
# JavaScript ์‹คํ–‰ ์™„๋ฃŒ ๋Œ€๊ธฐ
driver.execute_script("return document.readyState") == "complete"
# ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜
screenshot = driver.get_screenshot_as_png()
img = Image.open(BytesIO(screenshot))
buffered = BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode()
except WebDriverException as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜ ์‹คํŒจ: {str(e)} for URL: {url}")
return None
except Exception as e:
print(f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {str(e)} for URL: {url}")
return None
finally:
if 'driver' in locals():
driver.quit()
USERNAME = "openfree"
def format_timestamp(timestamp):
if not timestamp:
return 'N/A'
try:
# ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ
if isinstance(timestamp, str):
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
# ์ •์ˆ˜(๋ฐ€๋ฆฌ์ดˆ)์ธ ๊ฒฝ์šฐ
elif isinstance(timestamp, (int, float)):
dt = datetime.fromtimestamp(timestamp / 1000) # ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ์ดˆ๋กœ ๋ณ€ํ™˜
else:
return 'N/A'
return dt.strftime('%Y-%m-%d %H:%M')
except Exception as e:
print(f"Timestamp conversion error: {str(e)} for timestamp: {timestamp}")
return 'N/A'
def should_exclude_space(space_name):
"""ํŠน์ • ์ŠคํŽ˜์ด์Šค๋ฅผ ์ œ์™ธํ•˜๋Š” ํ•„ํ„ฐ ํ•จ์ˆ˜"""
exclude_keywords = [
'mixgen3', 'ginid', 'mouse', 'flxtrainlora',
'vidslicegpu', 'stickimg', 'ultpixgen', 'SORA',
'badassgi', 'newsplus', 'chargen', 'news',
'testhtml'
]
return any(keyword.lower() in space_name.lower() for keyword in exclude_keywords)
def get_pastel_color(index):
"""Generate unique pastel colors based on index"""
pastel_colors = [
'#FFE6E6', # ์—ฐํ•œ ๋ถ„ํ™
'#FFE6FF', # ์—ฐํ•œ ๋ณด๋ผ
'#E6E6FF', # ์—ฐํ•œ ํŒŒ๋ž‘
'#E6FFFF', # ์—ฐํ•œ ํ•˜๋Š˜
'#E6FFE6', # ์—ฐํ•œ ์ดˆ๋ก
'#FFFFE6', # ์—ฐํ•œ ๋…ธ๋ž‘
'#FFF0E6', # ์—ฐํ•œ ์ฃผํ™ฉ
'#F0E6FF', # ์—ฐํ•œ ๋ผ๋ฒค๋”
'#FFE6F0', # ์—ฐํ•œ ๋กœ์ฆˆ
'#E6FFF0', # ์—ฐํ•œ ๋ฏผํŠธ
'#F0FFE6', # ์—ฐํ•œ ๋ผ์ž„
'#FFE6EB', # ์—ฐํ•œ ์ฝ”๋ž„
'#E6EBFF', # ์—ฐํ•œ ํผํ”Œ๋ธ”๋ฃจ
'#FFE6F5', # ์—ฐํ•œ ํ•‘ํฌ
'#E6FFF5', # ์—ฐํ•œ ํ„ฐ์ฝ”์ด์ฆˆ
'#F5E6FF', # ์—ฐํ•œ ๋ชจ๋ธŒ
'#FFE6EC', # ์—ฐํ•œ ์‚ด๋ชฌ
'#E6FFEC', # ์—ฐํ•œ ์Šคํ”„๋ง๊ทธ๋ฆฐ
'#ECE6FF', # ์—ฐํ•œ ํŽ˜๋ฆฌ์œ™ํด
'#FFE6F7', # ์—ฐํ•œ ๋งค๊ทธ๋†€๋ฆฌ์•„
]
return pastel_colors[index % len(pastel_colors)]
def get_space_card(space, index):
"""Generate HTML card for a space with colorful design and lots of emojis"""
space_id = space.get('id', '')
space_name = space_id.split('/')[-1]
likes = space.get('likes', 0)
created_at = format_timestamp(space.get('createdAt'))
sdk = space.get('sdk', 'N/A')
# SDK๋ณ„ ์ด๋ชจ์ง€ ๋ฐ ๊ด€๋ จ ์ด๋ชจ์ง€ ์„ธํŠธ
sdk_emoji_sets = {
'gradio': {
'main': '๐ŸŽจ',
'related': ['๐Ÿ–ผ๏ธ', '๐ŸŽญ', '๐ŸŽช', '๐ŸŽ ', '๐ŸŽก', '๐ŸŽข', '๐ŸŽฏ', '๐ŸŽฒ', '๐ŸŽฐ', '๐ŸŽณ']
},
'streamlit': {
'main': 'โšก',
'related': ['๐Ÿ’ซ', 'โœจ', 'โญ', '๐ŸŒŸ', '๐Ÿ’ฅ', 'โšก', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽ†', '๐ŸŽ‡']
},
'docker': {
'main': '๐Ÿณ',
'related': ['๐Ÿ‹', '๐ŸŒŠ', '๐ŸŒ', '๐Ÿšข', 'โ›ด๏ธ', '๐Ÿ›ฅ๏ธ', '๐Ÿ ', '๐Ÿก', '๐Ÿฆˆ', '๐Ÿฌ']
},
'static': {
'main': '๐Ÿ“„',
'related': ['๐Ÿ“', '๐Ÿ“ฐ', '๐Ÿ“‘', '๐Ÿ—‚๏ธ', '๐Ÿ“', '๐Ÿ“‚', '๐Ÿ“š', '๐Ÿ“–', '๐Ÿ“’', '๐Ÿ“”']
},
'panel': {
'main': '๐Ÿ“Š',
'related': ['๐Ÿ“ˆ', '๐Ÿ“‰', '๐Ÿ’น', '๐Ÿ“‹', '๐Ÿ“Œ', '๐Ÿ“', '๐Ÿ—บ๏ธ', '๐ŸŽฏ', '๐Ÿ“', '๐Ÿ“']
},
'N/A': {
'main': '๐Ÿ”ง',
'related': ['๐Ÿ”จ', 'โš’๏ธ', '๐Ÿ› ๏ธ', 'โš™๏ธ', '๐Ÿ”ฉ', 'โ›๏ธ', 'โšก', '๐Ÿ”Œ', '๐Ÿ’ก', '๐Ÿ”‹']
}
}
# SDK์— ๋”ฐ๋ฅธ ์ด๋ชจ์ง€ ์„ ํƒ
sdk_lower = sdk.lower()
bg_color = get_pastel_color(index) # ์ธ๋ฑ์Šค ๊ธฐ๋ฐ˜ ์ƒ‰์ƒ ์„ ํƒ
emoji_set = sdk_emoji_sets.get(sdk_lower, sdk_emoji_sets['N/A'])
main_emoji = emoji_set['main']
# ๋žœ๋คํ•˜๊ฒŒ 3๊ฐœ์˜ ๊ด€๋ จ ์ด๋ชจ์ง€ ์„ ํƒ
decorative_emojis = random.sample(emoji_set['related'], 3)
# ์ถ”๊ฐ€ ์žฅ์‹์šฉ ์ด๋ชจ์ง€
general_emojis = ['๐Ÿš€', '๐Ÿ’ซ', 'โญ', '๐ŸŒŸ', 'โœจ', '๐Ÿ’ฅ', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽฏ', '๐ŸŽจ',
'๐ŸŽญ', '๐ŸŽช', '๐ŸŽข', '๐ŸŽก', '๐ŸŽ ', '๐ŸŽช', '๐ŸŽญ', '๐ŸŽจ', '๐ŸŽฏ', '๐ŸŽฒ']
random_emojis = random.sample(general_emojis, 3)
# ์ข‹์•„์š” ์ˆ˜์— ๋”ฐ๋ฅธ ํ•˜ํŠธ ์ด๋ชจ์ง€
heart_emoji = 'โค๏ธ' if likes > 100 else '๐Ÿ’–' if likes > 50 else '๐Ÿ’' if likes > 10 else '๐Ÿค'
return f"""
<div style='border: none;
padding: 25px;
margin: 15px;
border-radius: 20px;
background-color: {bg_color};
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.3s ease-in-out;
position: relative;
overflow: hidden;'
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
<div style='position: absolute; top: -15px; right: -15px; font-size: 100px; opacity: 0.1;'>
{main_emoji}
</div>
<div style='position: absolute; top: 10px; right: 10px; font-size: 20px;'>
{decorative_emojis[0]}
</div>
<div style='position: absolute; bottom: 10px; left: 10px; font-size: 20px;'>
{decorative_emojis[1]}
</div>
<div style='position: absolute; top: 50%; right: 10px; font-size: 20px;'>
{decorative_emojis[2]}
</div>
<h3 style='color: #2d2d2d;
margin: 0 0 20px 0;
font-size: 1.4em;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
<a href='https://huggingface.co/spaces/{space_id}' target='_blank'
style='text-decoration: none; color: #2d2d2d;'>
{space_name}
</a>
<span style='font-size: 1.3em'>{random_emojis[1]}</span>
</h3>
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>SDK:</strong> {main_emoji} {sdk} {decorative_emojis[0]}
</p>
<p style='margin: 8px 0;'>
<strong>Created:</strong> ๐Ÿ“… {created_at} โฐ
</p>
<p style='margin: 8px 0;'>
<strong>Likes:</strong> {heart_emoji} {likes} {random_emojis[2]}
</p>
</div>
<div style='margin-top: 20px;
display: flex;
justify-content: space-between;
align-items: center;'>
<a href='https://huggingface.co/spaces/{space_id}' target='_blank'
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
color: white;
padding: 10px 20px;
border-radius: 15px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0,132,255,0.3);'
onmouseover='this.style.transform="scale(1.05)"; this.style.boxShadow="0 4px 12px rgba(0,132,255,0.4)"'
onmouseout='this.style.transform="scale(1)"; this.style.boxShadow="0 2px 8px rgba(0,132,255,0.3)"'>
<span>View Space</span> ๐Ÿš€ {random_emojis[0]}
</a>
<span style='color: #666; font-size: 0.9em; opacity: 0.7;'>
๐Ÿ†” {space_id} {decorative_emojis[2]}
</span>
</div>
</div>
"""
def get_vercel_deployments():
"""Vercel API๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ๋ฐฐํฌ๋œ ์„œ๋น„์Šค ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ ์šฉ)"""
token = "A8IFZmgW2cqA4yUNlLPnci0N"
base_url = "https://api.vercel.com/v6/deployments"
all_deployments = []
has_next = True
page = 1
until = None # ์ฒซ ์š”์ฒญ์—์„œ๋Š” until ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
try:
while has_next:
# URL ๊ตฌ์„ฑ (ํŽ˜์ด์ง€๋„ค์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ)
url = f"{base_url}?limit=100"
if until:
url += f"&until={until}"
print(f"Fetching page {page}... URL: {url}") # ๋””๋ฒ„๊น…์šฉ
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"Vercel API Error: {response.text}")
break
data = response.json()
current_deployments = data.get('deployments', [])
if not current_deployments: # ๋” ์ด์ƒ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ
break
all_deployments.extend(current_deployments)
# ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ์œ„ํ•œ until ๊ฐ’ ์„ค์ •
pagination = data.get('pagination', {})
until = pagination.get('next')
has_next = bool(until) # until ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ
print(f"Page {page} fetched. Got {len(current_deployments)} deployments") # ๋””๋ฒ„๊น…์šฉ
page += 1
print(f"Total deployments fetched: {len(all_deployments)}") # ๋””๋ฒ„๊น…์šฉ
# ์ƒํƒœ๊ฐ€ 'READY'์ด๊ณ  'url'์ด ์žˆ๋Š” ๋ฐฐํฌ๋งŒ ํ•„ํ„ฐ๋งํ•˜๊ณ  'javis1' ์ œ์™ธ
active_deployments = [
dep for dep in all_deployments
if dep.get('state') == 'READY' and
dep.get('url') and
'javis1' not in dep.get('name', '').lower()
]
print(f"Active deployments after filtering: {len(active_deployments)}") # ๋””๋ฒ„๊น…์šฉ
return active_deployments
except Exception as e:
print(f"Error fetching Vercel deployments: {str(e)}")
return []
def get_vercel_card(deployment, index, is_top_best=False):
"""Vercel ๋ฐฐํฌ ์นด๋“œ HTML ์ƒ์„ฑ ํ•จ์ˆ˜"""
raw_url = deployment.get('url', '')
# URL ์ฒ˜๋ฆฌ
if raw_url.startswith('http'):
url = raw_url
else:
url = f"https://{raw_url}"
name = deployment.get('name', '์ด๋ฆ„ ์—†๋Š” ํ”„๋กœ์ ํŠธ')
# ์นด๋“œ ID ์ƒ์„ฑ
card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}"
# Top Best ํ•ญ๋ชฉ์ผ ๊ฒฝ์šฐ์˜ ์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ
screenshot_html = ""
if is_top_best:
try:
print(f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์‹œ๋„: {url}") # ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ
screenshot_base64 = take_screenshot(raw_url)
if screenshot_base64:
screenshot_html = f"""
<div style="width: 100%; height: 200px; overflow: hidden; border-radius: 10px; margin-bottom: 15px;">
<img src="data:image/png;base64,{screenshot_base64}"
style="width: 100%; height: 100%; object-fit: cover;"
alt="{name} ์Šคํฌ๋ฆฐ์ƒท"/>
</div>
"""
else:
print(f"์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜ ์‹คํŒจ: {url}") # ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ
except Exception as e:
print(f"์Šคํฌ๋ฆฐ์ƒท ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)} for URL: {url}") # ๋””๋ฒ„๊น…์šฉ ๋กœ๊ทธ
bg_color = get_pastel_color(index + (20 if not is_top_best else 0))
tech_emojis = ['โšก', '๐Ÿš€', '๐ŸŒŸ', 'โœจ', '๐Ÿ’ซ', '๐Ÿ”ฅ', '๐ŸŒˆ', '๐ŸŽฏ', '๐ŸŽจ', '๐Ÿ”ฎ']
random_emojis = random.sample(tech_emojis, 3)
# Top Best ์นด๋“œ์˜ ๊ฐ„์†Œํ™”๋œ ์ •๋ณด ์„น์…˜
if is_top_best:
info_section = f"""
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>URL:</strong> ๐Ÿ”— {url}
</p>
</div>
"""
else:
info_section = f"""
<div style='margin: 15px 0; color: #444; background: rgba(255,255,255,0.5);
padding: 15px; border-radius: 12px;'>
<p style='margin: 8px 0;'>
<strong>Status:</strong> โœ… {deployment.get('state', 'N/A')}
</p>
<p style='margin: 8px 0;'>
<strong>Created:</strong> ๐Ÿ“… {format_timestamp(deployment.get('created'))}
</p>
<p style='margin: 8px 0;'>
<strong>URL:</strong> ๐Ÿ”— {url}
</p>
</div>
"""
return f"""
<div id="{card_id}" class="vercel-card"
data-likes="0"
style='border: none;
padding: 25px;
margin: 15px;
border-radius: 20px;
background-color: {bg_color};
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: all 0.3s ease-in-out;
position: relative;
overflow: hidden;'
onmouseover='this.style.transform="translateY(-5px) scale(1.02)"; this.style.boxShadow="0 8px 25px rgba(0,0,0,0.15)"'
onmouseout='this.style.transform="translateY(0) scale(1)"; this.style.boxShadow="0 4px 15px rgba(0,0,0,0.1)"'>
{screenshot_html}
<h3 style='color: #2d2d2d;
margin: 0 0 20px 0;
font-size: 1.4em;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.3em'>{random_emojis[0]}</span>
<a href='{url}' target='_blank'
style='text-decoration: none; color: #2d2d2d;'>
{name}
</a>
<span style='font-size: 1.3em'>{random_emojis[1]}</span>
</h3>
{info_section}
<div style='margin-top: 20px; display: flex; justify-content: space-between; align-items: center;'>
<div class="like-section" style="display: flex; align-items: center; gap: 10px;">
<button onclick="toggleLike('{card_id}')" class="like-button"
style="background: none; border: none; cursor: pointer; font-size: 1.5em; padding: 5px 10px;">
๐Ÿค
</button>
<span class="like-count" style="font-size: 1.2em; color: #666;">0</span>
</div>
<a href='{url}' target='_blank'
style='background: linear-gradient(45deg, #0084ff, #00a3ff);
color: white;
padding: 10px 20px;
border-radius: 15px;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0,132,255,0.3);'
onmouseover='this.style.transform="scale(1.05)"; this.style.boxShadow="0 4px 12px rgba(0,132,255,0.4)"'
onmouseout='this.style.transform="scale(1)"; this.style.boxShadow="0 2px 8px rgba(0,132,255,0.3)"'>
<span>View Deployment</span> ๐Ÿš€ {random_emojis[0]}
</a>
</div>
</div>
"""
# Top Best URLs ์ •์˜
TOP_BEST_URLS = [
{
"url": "dekvxz.vercel.app",
"name": "[๊ฒŒ์ž„] ๋‹ค์ด์–ดํŠธ ํ—Œํ„ฐ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "jtufui.vercel.app",
"name": "[๊ฒŒ์ž„] ํ…Œ๋Ÿฌ๋ฆฌ์ŠคํŠธ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ggumim",
"name": "[MOUSE-II] ์ด๋ฏธ์ง€์— ํ•œ๊ธ€ ์ถœ๋ ฅ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "xabtnc.vercel.app",
"name": "[ChatGPT] ๋‚˜๋งŒ์˜ LLM",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ifbhdc",
"name": "[๊ฒŒ์ž„] ๋ณด์„ ํŒกํŒก",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "nxhquk.vercel.app",
"name": "[๊ฒŒ์ž„] ํ…ŒํŠธ๋ฆฌ์Šค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "bydcnd.vercel.app",
"name": "[๋ชจ๋ธ] 3D ๋ถ„์ž ๋ชจํ˜•",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "ijhama.vercel.app",
"name": "ํˆฌ์ž ํฌํŠธํด๋ฆฌ์˜ค ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "oschnl.vercel.app",
"name": "๋กœ๋˜ ๋ฒˆํ˜ธ ๋ถ„์„/์ถ”์ฒœ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "rzwzrq.vercel.app",
"name": "์—‘์…€/CSV ๋ฐ์ดํ„ฐ ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "twkqre.vercel.app",
"name": "[์šด์„ธ] ํƒ€๋กœ์นด๋“œ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "htwymz.vercel.app",
"name": "[๊ฒŒ์ž„] ์†Œ๋ฐฉํ—ฌ๊ธฐ",
"created": "2024-11-20 00:00",
"state": "READY"
},
{
"url": "mktmbn.vercel.app",
"name": "[๊ฒŒ์ž„] ์šฐ์ฃผ์ „์Ÿ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "euguwt.vercel.app",
"name": "[๊ฒŒ์ž„] ํฌ์„ธ์ด๋ˆ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "qmdzoh.vercel.app",
"name": "[๊ฒŒ์ž„] ํ•˜๋Š˜์„ ์ง€์ผœ๋ผ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "kofaqo.vercel.app",
"name": "[๊ฒŒ์ž„] ์šด์„ ์ถฉ๋Œ!",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "qoqqkq.vercel.app",
"name": "[๊ฒŒ์ž„] ๋‘๋”์ฅ ์žก๊ธฐ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "nmznel.vercel.app",
"name": "[๊ฒŒ์ž„] ๊ณ ์–‘์ด ์ „์šฉ",
"created": "2024-11-19 00:00",
"state": "READY"
},
{
"url": "psrrtp.vercel.app",
"name": "[๋Œ€์‹œ๋ณด๋“œ] ์„ธ๊ณ„ ์ธ๊ตฌ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "xxloav.vercel.app",
"name": "[๊ฒŒ์ž„] ๋ฒฝ๋Œ ๊นจ๊ธฐ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/edpaje",
"name": "[๊ฒŒ์ž„] ๊ธฐ์–ต๋ ฅ ์นด๋“œ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://huggingface.co/spaces/openfree/ixtidb",
"name": "AI ์š”๋ฆฌ์‚ฌ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "cnlzji.vercel.app",
"name": "๊ตญ๊ฐ€ ์ •๋ณด ๋น„๊ต",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "fazely.vercel.app",
"name": "Wikipedia ์ง€์‹ ๋ถ„์„",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "pkzhbo.vercel.app",
"name": "์„ธ๊ณ„ ๊ตญ๊ฐ€๋ณ„ ์‹œ๊ฐ„๋Œ€",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "pammgl.vercel.app",
"name": "๋ณด๋„์ž๋ฃŒ ๋ฐฐํฌ ์„œ๋น„์Šค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "https://ktduhm.vercel.app/",
"name": "์ˆ˜ํ•™์„ ๊ทธ๋ž˜ํ”„๋กœ ์ดํ•ด",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "vjmfoy.vercel.app",
"name": "[๊ฒŒ์ž„] 3D ๋ฒฝ๋Œ์Œ“๊ธฐ",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "aodakf.vercel.app",
"name": "[๋ฒ„์ถ”์–ผ] 3D ๊ฐ€์ƒํ˜„์‹ค",
"created": "2024-11-18 00:00",
"state": "READY"
},
{
"url": "mxoeue.vercel.app",
"name": "์Œ์„ฑ ์ƒ์„ฑ(TTS),์กฐ์ •",
"created": "2024-11-18 00:00",
"state": "READY"
}
]
def get_user_spaces():
# ๊ธฐ์กด Hugging Face ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
url = f"https://huggingface.co/api/spaces?author={USERNAME}&limit=500"
headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
try:
# Hugging Face ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
response = requests.get(url, headers=headers)
spaces_data = response.json() if response.status_code == 200 else []
# ์ œ์™ธํ•  ์ŠคํŽ˜์ด์Šค ํ•„ํ„ฐ๋ง
user_spaces = [
space for space in spaces_data
if not should_exclude_space(space.get('id', '').split('/')[-1])
]
# TOP_BEST_URLS ํ•ญ๋ชฉ ์ˆ˜
top_best_count = len(TOP_BEST_URLS)
# Vercel API๋ฅผ ํ†ตํ•œ ์‹ค์ œ ๋ฐฐํฌ ์ˆ˜
vercel_deployments = get_vercel_deployments()
actual_vercel_count = len(vercel_deployments) if vercel_deployments else 0
html_content = f"""
<div style='
min-height: 100vh;
background: linear-gradient(135deg, #f6f8ff 0%, #f0f4ff 100%);
background-image: url("data:image/svg+xml,%3Csvg width='100' height='20' viewBox='0 0 100 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.184 20c.357-.13.72-.264 1.088-.402l1.768-.661C33.64 15.347 39.647 14 50 14c10.271 0 15.362 1.222 24.629 4.928.955.383 1.869.74 2.75 1.072h6.225c-2.51-.73-5.139-1.691-8.233-2.928C65.888 13.278 60.562 12 50 12c-10.626 0-16.855 1.397-26.66 5.063l-1.767.662c-2.475.923-4.66 1.674-6.724 2.275h6.335zm0-20C13.258 2.892 8.077 4 0 4V2c5.744 0 9.951-.574 14.85-2h6.334zM77.38 0C85.239 2.966 90.502 4 100 4V2c-6.842 0-11.386-.542-16.396-2h-6.225zM0 14c8.44 0 13.718-1.21 22.272-4.402l1.768-.661C33.64 5.347 39.647 4 50 4c10.271 0 15.362 1.222 24.629 4.928C84.112 12.722 89.438 14 100 14v-2c-10.271 0-15.362-1.222-24.629-4.928C65.888 3.278 60.562 2 50 2 39.374 2 33.145 3.397 23.34 7.063l-1.767.662C13.223 10.84 8.163 12 0 12v2z' fill='%23f0f0f0' fill-opacity='0.2' fill-rule='evenodd'/%3E%3C/svg%3E");
padding: 40px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;'>
<!-- ๋ฉ”์ธ ํ—ค๋” -->
<div style='
background: rgba(255, 255, 255, 0.8);
border-radius: 20px;
padding: 30px;
margin-bottom: 40px;
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.8);'>
<h2 style='
color: #2d2d2d;
margin: 0 0 15px 0;
font-size: 2em;
background: linear-gradient(45deg, #2d2d2d, #0084ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;'>
๊ณต๊ฐœ ๊ฐค๋Ÿฌ๋ฆฌ(์ƒ์„ฑ Web/App) by MOUSE
</h2>
<div style='
background: linear-gradient(45deg, #0084ff, #00a3ff);
border-radius: 10px;
padding: 15px;
margin: 20px 0;'>
<a href='https://openfree-mouse.hf.space'
target='_blank'
style='
color: white;
text-decoration: none;
font-size: 1.1em;
display: block;
text-align: center;'>
๐Ÿš€ ํ”„๋กฌํ”„ํŠธ๋งŒ์œผ๋กœ ๋‚˜๋งŒ์˜ ์›น์„œ๋น„์Šค๋ฅผ ์ฆ‰์‹œ ์ƒ์„ฑํ•˜๋Š” MOUSE
</a>
</div>
<p style='
color: #666;
margin: 0;
font-size: 0.9em;
text-align: center;
background: rgba(255,255,255,0.5);
padding: 10px;
border-radius: 10px;'>
Found {actual_vercel_count} Vercel deployments and {len(user_spaces)} Hugging Face spaces<br>
(Plus {top_best_count} featured items in Top Best section)
</p>
</div>
<!-- Top Best ์„น์…˜ -->
<div class="section-container" style='
background: rgba(255, 255, 255, 0.4);
border-radius: 20px;
padding: 30px;
margin: 20px 0;
backdrop-filter: blur(10px);'>
<h3 style='
color: #2d2d2d;
margin: 0 0 20px 0;
padding: 15px 25px;
background: rgba(255,255,255,0.7);
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
border-left: 5px solid #0084ff;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.5em;'>๐Ÿ†</span>
Top Best
</h3>
<div style='
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;'>
{"".join(get_vercel_card(
{"url": url["url"], "created": url["created"], "name": url["name"], "state": url["state"]},
idx,
is_top_best=True
) for idx, url in enumerate(TOP_BEST_URLS))}
</div>
</div>
<!-- Vercel Deployments ์„น์…˜ -->
{f'''
<div class="section-container" style='
background: rgba(255, 255, 255, 0.4);
border-radius: 20px;
padding: 30px;
margin: 20px 0;
backdrop-filter: blur(10px);'>
<h3 style='
color: #2d2d2d;
margin: 0 0 20px 0;
padding: 15px 25px;
background: rgba(255,255,255,0.7);
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
border-left: 5px solid #00a3ff;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.5em;'>โšก</span>
Vercel Deployments
</h3>
<div id="vercel-container" style='
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;'>
{"".join(get_vercel_card(dep, idx) for idx, dep in enumerate(vercel_deployments))}
</div>
</div>
''' if vercel_deployments else ''}
<!-- Hugging Face Spaces ์„น์…˜ -->
<div class="section-container" style='
background: rgba(255, 255, 255, 0.4);
border-radius: 20px;
padding: 30px;
margin: 20px 0;
backdrop-filter: blur(10px);'>
<h3 style='
color: #2d2d2d;
margin: 0 0 20px 0;
padding: 15px 25px;
background: rgba(255,255,255,0.7);
border-radius: 15px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
border-left: 5px solid #ff6b6b;
display: flex;
align-items: center;
gap: 10px;'>
<span style='font-size: 1.5em;'>๐Ÿค—</span>
Hugging Face Spaces
</h3>
<div style='
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;'>
{"".join(get_space_card(space, idx) for idx, space in enumerate(user_spaces))}
</div>
</div>
</div>
<!-- ๊ธฐ์กด JavaScript ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ -->
<script>
// ... (๊ธฐ์กด JavaScript ์ฝ”๋“œ)
</script>
"""
return html_content
except Exception as e:
print(f"Error: {str(e)}")
return f"""
<div style='padding: 20px; text-align: center; color: #666;'>
<h2>Error occurred while fetching spaces</h2>
<p>Error details: {str(e)}</p>
<p>Please try again later.</p>
</div>
"""
# Creating the Gradio interface
demo = gr.Blocks()
with demo:
html_output = gr.HTML(value=get_user_spaces())
if __name__ == "__main__":
demo.launch()