import requests import gradio as gr from datetime import datetime import random from selenium import webdriver from selenium.common.exceptions import WebDriverException from PIL import Image from io import BytesIO import base64 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) driver.implicitly_wait(10) 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)}") 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', '이름 없는 프로젝트') created = format_timestamp(deployment.get('created')) state = deployment.get('state', 'N/A') # 카드 ID 생성 card_id = f"vercel-card-{url.replace('.', '-').replace('/', '-')}" # Top Best 항목일 경우의 스크린샷 처리 screenshot_html = "" if is_top_best: try: screenshot_base64 = take_screenshot(raw_url) if screenshot_base64: screenshot_html = f"""
{name} 스크린샷
""" except Exception as e: print(f"스크린샷 처리 오류: {str(e)}") 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: ✅ {state}

Created: 📅 {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 https://openfree-mouse.hf.space

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()