Spaces:
Sleeping
Sleeping
| import os, re, json, sqlite3 | |
| from flask import Flask, render_template, request, jsonify | |
| app = Flask(__name__) | |
| # ββββββββββββββββββββββββββ κ²½λ‘λ₯Ό μ λ κ²½λ‘λ‘ μ§μ ββββββββββββββββββββββββββ | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| DB_FILE = os.path.join(BASE_DIR, "favorite_sites.json") | |
| SQLITE_DB = os.path.join(BASE_DIR, "favorite_sites.db") | |
| # μ΄ν κΈ°μ‘΄ μ½λ λμΌ | |
| BLOCKED_DOMAINS = [ | |
| "naver.com", "daum.net", "google.com", | |
| "facebook.com", "instagram.com", "kakao.com", | |
| "ycombinator.com" | |
| ] | |
| CATEGORIES = { | |
| "Productivity": [ | |
| "https://huggingface.co/spaces/ginigen/perflexity-clone", | |
| "https://huggingface.co/spaces/ginipick/IDEA-DESIGN", | |
| "https://huggingface.co/spaces/VIDraft/mouse-webgen", | |
| "https://huggingface.co/spaces/openfree/Vibe-Game", | |
| "https://huggingface.co/spaces/openfree/Game-Gallery", | |
| "https://huggingface.co/spaces/aiqtech/Contributors-Leaderboard", | |
| "https://huggingface.co/spaces/fantaxy/Model-Leaderboard", | |
| "https://huggingface.co/spaces/fantaxy/Space-Leaderboard", | |
| "https://huggingface.co/spaces/openfree/Korean-Leaderboard", | |
| ], | |
| "Multimodal": [ | |
| "https://huggingface.co/spaces/openfree/DreamO-video", | |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo", | |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored", | |
| "https://huggingface.co/spaces/fantaxy/Sound-AI-SFX", | |
| "https://huggingface.co/spaces/ginigen/SFX-Sound-magic", | |
| "https://huggingface.co/spaces/ginigen/VoiceClone-TTS", | |
| "https://huggingface.co/spaces/aiqcamp/MCP-kokoro", | |
| "https://huggingface.co/spaces/aiqcamp/ENGLISH-Speaking-Scoring", | |
| ], | |
| "Professional": [ | |
| "https://huggingface.co/spaces/ginigen/blogger", | |
| "https://huggingface.co/spaces/VIDraft/money-radar", | |
| "https://huggingface.co/spaces/immunobiotech/drug-discovery", | |
| "https://huggingface.co/spaces/immunobiotech/Gemini-MICHELIN", | |
| "https://huggingface.co/spaces/Heartsync/Papers-Leaderboard", | |
| "https://huggingface.co/spaces/VIDraft/PapersImpact", | |
| "https://huggingface.co/spaces/ginipick/AgentX-Papers", | |
| "https://huggingface.co/spaces/openfree/Cycle-Navigator", | |
| ], | |
| "Image": [ | |
| "https://huggingface.co/spaces/ginigen/interior-design", | |
| "https://huggingface.co/spaces/ginigen/Workflow-Canvas", | |
| "https://huggingface.co/spaces/ginigen/Multi-LoRAgen", | |
| "https://huggingface.co/spaces/ginigen/Every-Text", | |
| "https://huggingface.co/spaces/ginigen/text3d-r1", | |
| "https://huggingface.co/spaces/ginipick/FLUXllama", | |
| "https://huggingface.co/spaces/Heartsync/FLUX-Vision", | |
| "https://huggingface.co/spaces/ginigen/VisualCloze", | |
| "https://huggingface.co/spaces/seawolf2357/Ghibli-Multilingual-Text-rendering", | |
| "https://huggingface.co/spaces/ginigen/Ghibli-Meme-Studio", | |
| "https://huggingface.co/spaces/VIDraft/Open-Meme-Studio", | |
| "https://huggingface.co/spaces/ginigen/3D-LLAMA", | |
| ], | |
| "LLM / VLM": [ | |
| "https://huggingface.co/spaces/VIDraft/Gemma-3-R1984-4B", | |
| "https://huggingface.co/spaces/VIDraft/Gemma-3-R1984-12B", | |
| "https://huggingface.co/spaces/ginigen/Mistral-Perflexity", | |
| "https://huggingface.co/spaces/aiqcamp/gemini-2.5-flash-preview", | |
| "https://huggingface.co/spaces/openfree/qwen3-30b-a3b-research", | |
| "https://huggingface.co/spaces/openfree/qwen3-235b-a22b-research", | |
| "https://huggingface.co/spaces/openfree/Llama-4-Maverick-17B-Research", | |
| ], | |
| } | |
| def init_db(): | |
| if not os.path.exists(DB_FILE): | |
| with open(DB_FILE, "w", encoding="utf-8") as f: | |
| json.dump([], f, ensure_ascii=False) | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS urls ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| url TEXT UNIQUE NOT NULL, | |
| date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| conn.commit() | |
| # JSONμ μλ URLμ (μ΄κΈ°ν μ) SQLiteλ‘λ λ³΅μ¬ | |
| json_urls = load_json() | |
| if json_urls: | |
| db_urls = load_db_sqlite() | |
| for url in json_urls: | |
| if url not in db_urls: | |
| add_url_to_sqlite(url) | |
| conn.close() | |
| def load_json(): | |
| """Load URLs from JSON file""" | |
| try: | |
| with open(DB_FILE, "r", encoding="utf-8") as f: | |
| raw = json.load(f) | |
| return raw if isinstance(raw, list) else [] | |
| except Exception: | |
| return [] | |
| def save_json(lst): | |
| """Save URLs to JSON file""" | |
| try: | |
| with open(DB_FILE, "w", encoding="utf-8") as f: | |
| json.dump(lst, f, ensure_ascii=False, indent=2) | |
| return True | |
| except Exception: | |
| return False | |
| def load_db_sqlite(): | |
| """Load URLs from SQLite""" | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT url FROM urls ORDER BY date_added DESC") | |
| urls = [row[0] for row in cursor.fetchall()] | |
| conn.close() | |
| return urls | |
| def add_url_to_sqlite(url): | |
| """Add a URL to SQLite database""" | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| try: | |
| cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,)) | |
| conn.commit() | |
| success = True | |
| except sqlite3.IntegrityError: | |
| success = False | |
| conn.close() | |
| return success | |
| def update_url_in_sqlite(old_url, new_url): | |
| """Update a URL in SQLite database""" | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| try: | |
| cursor.execute("UPDATE urls SET url = ? WHERE url = ?", (new_url, old_url)) | |
| if cursor.rowcount > 0: | |
| conn.commit() | |
| success = True | |
| else: | |
| success = False | |
| except sqlite3.IntegrityError: | |
| success = False | |
| conn.close() | |
| return success | |
| def delete_url_from_sqlite(url): | |
| """Delete a URL from SQLite database""" | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| cursor.execute("DELETE FROM urls WHERE url = ?", (url,)) | |
| if cursor.rowcount > 0: | |
| conn.commit() | |
| success = True | |
| else: | |
| success = False | |
| conn.close() | |
| return success | |
| def load_db(): | |
| """Load URLs: μ°μ SQLiteμμ λΆλ¬μ¨ λ€, μμΌλ©΄ JSON λΆλ¬μ€κΈ°""" | |
| urls = load_db_sqlite() | |
| if not urls: | |
| urls = load_json() | |
| for url in urls: | |
| add_url_to_sqlite(url) | |
| return urls | |
| def save_db(lst): | |
| """Save URLs to both SQLite and JSON""" | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| cursor.execute("DELETE FROM urls") | |
| for url in lst: | |
| cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,)) | |
| conn.commit() | |
| conn.close() | |
| return save_json(lst) | |
| def direct_url(hf_url): | |
| m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url) | |
| if not m: | |
| return hf_url | |
| owner, name = m.groups() | |
| owner = owner.lower() | |
| name = name.replace('.', '-').replace('_', '-').lower() | |
| return f"https://{owner}-{name}.hf.space" | |
| def screenshot_url(url): | |
| return f"https://image.thum.io/get/fullpage/{url}" | |
| def process_url_for_preview(url): | |
| if any(d for d in BLOCKED_DOMAINS if d in url): | |
| return screenshot_url(url), "snapshot" | |
| if "vibe-coding-tetris" in url or "World-of-Tank-GAME" in url or "Minesweeper-Game" in url: | |
| return screenshot_url(url), "snapshot" | |
| try: | |
| if "huggingface.co/spaces" in url: | |
| parts = url.rstrip("/").split("/") | |
| if len(parts) >= 5: | |
| owner = parts[-2] | |
| name = parts[-1] | |
| embed_url = f"https://huggingface.co/spaces/{owner}/{name}/embed" | |
| return embed_url, "iframe" | |
| except Exception: | |
| return screenshot_url(url), "snapshot" | |
| return url, "iframe" | |
| def api_category(): | |
| cat = request.args.get('name', '') | |
| urls = CATEGORIES.get(cat, []) | |
| return jsonify([ | |
| { | |
| "title": url.split('/')[-1], | |
| "owner": url.split('/')[-2] if '/spaces/' in url else '', | |
| "iframe": direct_url(url), | |
| "shot": screenshot_url(url), | |
| "hf": url | |
| } for url in urls | |
| ]) | |
| def api_favorites(): | |
| urls = load_db() | |
| page = int(request.args.get('page', 1)) | |
| per_page = int(request.args.get('per_page', 9)) | |
| total_pages = max(1, (len(urls) + per_page - 1) // per_page) | |
| start = (page - 1) * per_page | |
| end = min(start + per_page, len(urls)) | |
| urls_page = urls[start:end] | |
| result = [] | |
| for url in urls_page: | |
| try: | |
| preview_url, mode = process_url_for_preview(url) | |
| result.append({ | |
| "title": url.split('/')[-1], | |
| "url": url, | |
| "preview_url": preview_url, | |
| "mode": mode | |
| }) | |
| except Exception: | |
| result.append({ | |
| "title": url.split('/')[-1], | |
| "url": url, | |
| "preview_url": screenshot_url(url), | |
| "mode": "snapshot" | |
| }) | |
| return jsonify({ | |
| "items": result, | |
| "page": page, | |
| "total_pages": total_pages | |
| }) | |
| def add_url(): | |
| url = request.form.get('url', '').strip() | |
| if not url: | |
| return jsonify({"success": False, "message": "URL is required"}) | |
| conn = sqlite3.connect(SQLITE_DB) | |
| cursor = conn.cursor() | |
| try: | |
| cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,)) | |
| conn.commit() | |
| success = True | |
| except sqlite3.IntegrityError: | |
| success = False | |
| except Exception as e: | |
| print(f"SQLite error: {str(e)}") | |
| success = False | |
| finally: | |
| conn.close() | |
| if not success: | |
| return jsonify({"success": False, "message": "URL already exists or could not be added"}) | |
| # JSON νμΌμλ μΆκ° (λ°±μ μ©) | |
| data = load_json() | |
| if url not in data: | |
| data.insert(0, url) | |
| save_json(data) | |
| return jsonify({"success": True, "message": "URL added successfully"}) | |
| def update_url(): | |
| old = request.form.get('old', '') | |
| new = request.form.get('new', '').strip() | |
| if not new: | |
| return jsonify({"success": False, "message": "New URL is required"}) | |
| if not update_url_in_sqlite(old, new): | |
| return jsonify({"success": False, "message": "URL not found or new URL already exists"}) | |
| data = load_json() | |
| try: | |
| idx = data.index(old) | |
| data[idx] = new | |
| save_json(data) | |
| except ValueError: | |
| data.append(new) | |
| save_json(data) | |
| return jsonify({"success": True, "message": "URL updated successfully"}) | |
| def delete_url(): | |
| url = request.form.get('url', '') | |
| if not delete_url_from_sqlite(url): | |
| return jsonify({"success": False, "message": "URL not found"}) | |
| data = load_json() | |
| try: | |
| data.remove(url) | |
| save_json(data) | |
| except ValueError: | |
| pass | |
| return jsonify({"success": True, "message": "URL deleted successfully"}) | |
| def home(): | |
| os.makedirs('templates', exist_ok=True) | |
| with open('templates/index.html', 'w', encoding='utf-8') as fp: | |
| fp.write(r'''<!DOCTYPE html> | |
| <!-- μ΄ν HTML ν νλ¦Ώμ κΈ°μ‘΄ μ½λ κ·Έλλ‘ μ μ§ --> | |
| <!-- ... --> | |
| ''') | |
| return render_template('index.html', cats=list(CATEGORIES.keys())) | |
| init_db() | |
| def ensure_db_consistency(): | |
| urls = load_db_sqlite() | |
| save_json(urls) | |
| def before_request_func(): | |
| if not hasattr(app, '_got_first_request'): | |
| ensure_db_consistency() | |
| app._got_first_request = True | |
| if __name__ == '__main__': | |
| print("Initializing database...") | |
| init_db() | |
| db_path = os.path.abspath(SQLITE_DB) | |
| print(f"SQLite DB path: {db_path}") | |
| if os.path.exists(db_path): | |
| print(f"Database file exists, size: {os.path.getsize(db_path)} bytes") | |
| else: | |
| print("Warning: Database file does not exist after initialization!") | |
| app.run(host='0.0.0.0', port=7860) | |