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"""
{main_emoji}
{decorative_emojis[0]}
{decorative_emojis[1]}
{decorative_emojis[2]}

{random_emojis[0]} {space_name} {random_emojis[1]}

SDK: {main_emoji} {sdk} {decorative_emojis[0]}

Created: 📅 {created_at} ⏰

Likes: {heart_emoji} {likes} {random_emojis[2]}

View Space 🚀 {random_emojis[0]} 🆔 {space_id} {decorative_emojis[2]}
""" 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"""
{name} 스크린샷
""" 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"""

URL: 🔗 {url}

""" else: info_section = f"""

Status: ✅ {deployment.get('state', 'N/A')}

Created: 📅 {format_timestamp(deployment.get('created'))}

URL: 🔗 {url}

""" return f"""
{screenshot_html}

{random_emojis[0]} {name} {random_emojis[1]}

{info_section}
View Deployment 🚀 {random_emojis[0]}
""" # 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"""

공개 갤러리(생성 Web/App) by MOUSE

🚀 프롬프트만으로 나만의 웹서비스를 즉시 생성하는 MOUSE

Found {actual_vercel_count} Vercel deployments and {len(user_spaces)} Hugging Face spaces
(Plus {top_best_count} featured items in Top Best section)

🏆 Top Best

{"".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))}
{f'''

Vercel Deployments

{"".join(get_vercel_card(dep, idx) for idx, dep in enumerate(vercel_deployments))}
''' if vercel_deployments else ''}

🤗 Hugging Face Spaces

{"".join(get_space_card(space, idx) for idx, space in enumerate(user_spaces))}
""" return html_content except Exception as e: print(f"Error: {str(e)}") return f"""

Error occurred while fetching spaces

Error details: {str(e)}

Please try again later.

""" # Creating the Gradio interface demo = gr.Blocks() with demo: html_output = gr.HTML(value=get_user_spaces()) if __name__ == "__main__": demo.launch()