from flask import Flask, render_template, request, jsonify, session import requests from bs4 import BeautifulSoup import os from datetime import timedelta import logging import time # 로깅 설정 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) app = Flask(__name__) app.secret_key = os.urandom(24) app.permanent_session_lifetime = timedelta(days=7) # Hugging Face URL 목록 HUGGINGFACE_URLS = [ "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game", "https://huggingface.co/spaces/openfree/deepseek_r1_API", "https://huggingface.co/spaces/ginipick/open_Deep-Research", "https://huggingface.co/spaces/aiqmaster/open-deep-research", "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search", "https://huggingface.co/spaces/ginigen/LLaDA", "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal", "https://huggingface.co/spaces/ginigen/Ovis2-8B", "https://huggingface.co/spaces/ginigen/Graph-Mind", "https://huggingface.co/spaces/ginigen/Workflow-Canvas", "https://huggingface.co/spaces/ginigen/Design", "https://huggingface.co/spaces/ginigen/Diagram", "https://huggingface.co/spaces/ginigen/Mockup", "https://huggingface.co/spaces/ginigen/Infographic", "https://huggingface.co/spaces/ginigen/Flowchart", "https://huggingface.co/spaces/aiqcamp/FLUX-Vision", "https://huggingface.co/spaces/ginigen/VoiceClone-TTS", "https://huggingface.co/spaces/openfree/Perceptron-Network", "https://huggingface.co/spaces/openfree/Article-Generator", ] # URL에서 모델/스페이스 정보 추출 def extract_model_info(url): parts = url.split('/') if len(parts) < 6: return None if parts[3] == 'spaces' or parts[3] == 'models': return { 'type': parts[3], 'owner': parts[4], 'repo': parts[5], 'full_id': f"{parts[4]}/{parts[5]}" } elif len(parts) >= 5: return { 'type': 'models', 'owner': parts[3], 'repo': parts[4], 'full_id': f"{parts[3]}/{parts[4]}" } return None # URL의 마지막 부분을 제목으로 추출 def extract_title(url): parts = url.split("/") title = parts[-1] if parts else "" return title.replace("_", " ").replace("-", " ") # 허깅페이스 토큰 검증 def validate_token(token): headers = {"Authorization": f"Bearer {token}"} try: response = requests.get("https://huggingface.co/api/whoami-v2", headers=headers) if response.ok: return True, response.json() except Exception as e: logger.error(f"토큰 검증 오류: {e}") return False, None # 웹 스크래핑으로 좋아요 상태 확인 def check_like_status_by_scraping(url, token): headers = { "Authorization": f"Bearer {token}", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" } try: # 페이지 요청 response = requests.get(url, headers=headers) if not response.ok: logger.warning(f"페이지 요청 실패: {url}, 상태 코드: {response.status_code}") return False # HTML 파싱 soup = BeautifulSoup(response.text, 'html.parser') # 좋아요 버튼 찾기 (다양한 선택자 시도) like_button = None selectors = [ '.like-button-container button', 'button[aria-label="Like"]', 'button.like-button', 'button[data-testid="like-button"]', 'button.heart-button' ] for selector in selectors: like_button = soup.select_one(selector) if like_button: break if not like_button: logger.warning(f"좋아요 버튼을 찾을 수 없음: {url}") return False # 좋아요 상태 확인 (클래스, aria-pressed 속성 등으로 확인) is_liked = False # 클래스로 확인 if 'liked' in like_button.get('class', []) or 'active' in like_button.get('class', []): is_liked = True # aria-pressed 속성으로 확인 elif like_button.get('aria-pressed') == 'true': is_liked = True # 내부 텍스트 또는 아이콘으로 확인 elif like_button.find('span', class_='liked') or like_button.find('svg', class_='liked'): is_liked = True logger.info(f"스크래핑 결과: {url} - 좋아요 {is_liked}") return is_liked except Exception as e: logger.error(f"스크래핑 오류 ({url}): {e}") return False # 전체 URL 목록의 좋아요 상태 스크래핑 def scrape_all_like_status(token): like_status = {} for url in HUGGINGFACE_URLS: try: # 과도한 요청 방지를 위한 지연 time.sleep(1) is_liked = check_like_status_by_scraping(url, token) like_status[url] = is_liked logger.info(f"좋아요 상태 확인: {url} - {is_liked}") except Exception as e: logger.error(f"URL 처리 중 오류: {url} - {e}") like_status[url] = False return like_status @app.route('/') def home(): return render_template('index.html') @app.route('/api/login', methods=['POST']) def login(): token = request.form.get('token', '') if not token: return jsonify({'success': False, 'message': '토큰을 입력해주세요.'}) is_valid, user_info = validate_token(token) if not is_valid or not user_info: return jsonify({'success': False, 'message': '유효하지 않은 토큰입니다.'}) # 사용자 이름 찾기 username = None if 'name' in user_info: username = user_info['name'] elif 'user' in user_info and 'username' in user_info['user']: username = user_info['user']['username'] elif 'username' in user_info: username = user_info['username'] else: username = '인증된 사용자' # 세션에 저장 session['token'] = token session['username'] = username # 웹 스크래핑으로 좋아요 상태 확인 # 참고: 이 작업이 시간이 오래 걸릴 수 있으므로 비동기로 처리하는 것이 좋습니다 # 현재는 예시로 동기 방식으로 구현했습니다 try: like_status = scrape_all_like_status(token) session['like_status'] = like_status except Exception as e: logger.error(f"좋아요 상태 스크래핑 중 오류: {e}") session['like_status'] = {} return jsonify({ 'success': True, 'username': username }) @app.route('/api/logout', methods=['POST']) def logout(): session.pop('token', None) session.pop('username', None) session.pop('like_status', None) return jsonify({'success': True}) @app.route('/api/urls', methods=['GET']) def get_urls(): like_status = session.get('like_status', {}) results = [] for url in HUGGINGFACE_URLS: title = extract_title(url) model_info = extract_model_info(url) if not model_info: continue # 좋아요 상태 확인 is_liked = like_status.get(url, False) results.append({ 'url': url, 'title': title, 'model_info': model_info, 'is_liked': is_liked }) return jsonify(results) @app.route('/api/toggle-like', methods=['POST']) def toggle_like(): if 'token' not in session: return jsonify({'success': False, 'message': '로그인이 필요합니다.'}) data = request.json url = data.get('url') if not url: return jsonify({'success': False, 'message': 'URL이 필요합니다.'}) # 현재 좋아요 상태 확인 like_status = session.get('like_status', {}) current_status = like_status.get(url, False) # 실제로는 여기서 허깅페이스 API를 호출하여 좋아요 상태를 변경해야 합니다 # 현재는 예시로 세션에만 상태를 저장합니다 like_status[url] = not current_status session['like_status'] = like_status return jsonify({ 'success': True, 'is_liked': like_status[url], 'message': '좋아요를 추가했습니다.' if like_status[url] else '좋아요를 취소했습니다.' }) @app.route('/api/refresh-likes', methods=['POST']) def refresh_likes(): if 'token' not in session: return jsonify({'success': False, 'message': '로그인이 필요합니다.'}) try: # 웹 스크래핑으로 좋아요 상태 새로고침 like_status = scrape_all_like_status(session['token']) session['like_status'] = like_status return jsonify({ 'success': True, 'message': '좋아요 상태가 새로고침되었습니다.', 'like_status': like_status }) except Exception as e: logger.error(f"좋아요 상태 새로고침 중 오류: {e}") return jsonify({ 'success': False, 'message': f'좋아요 상태 새로고침 중 오류: {str(e)}' }) @app.route('/api/session-status', methods=['GET']) def session_status(): return jsonify({ 'logged_in': 'username' in session, 'username': session.get('username') }) if __name__ == '__main__': os.makedirs('templates', exist_ok=True) with open('templates/index.html', 'w', encoding='utf-8') as f: f.write(''' Hugging Face URL 카드 리스트
허깅페이스 계정: 로그인되지 않음

참고: 이 페이지는 웹 스크래핑 방식으로 좋아요 상태를 가져옵니다. 좋아요 상태가 정확하지 않거나 지연될 수 있습니다. '새로고침' 버튼을 클릭하여 최신 상태를 가져올 수 있습니다.

0개 중 0의 URL을 좋아요 했습니다.
''' ) app.run(debug=True, host='0.0.0.0', port=7860)