| from flask import Flask, render_template, request, jsonify |
| import os, re, json |
|
|
| app = Flask(__name__) |
|
|
| |
| |
| BLOCKED_DOMAINS = [ |
| "naver.com", "daum.net", "google.com", |
| "facebook.com", "instagram.com", "kakao.com", |
| "ycombinator.com" |
| ] |
|
|
| |
| CATEGORIES = { |
| "Popular": [ |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-REAL", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored", |
| "https://huggingface.co/spaces/Dagfinn1962/Midjourney-Free", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2", |
| "https://huggingface.co/spaces/Heartsync/Novel-NSFW", |
|
|
| "https://huggingface.co/spaces/erax/EraX-NSFW-V1.0", |
| "https://huggingface.co/spaces/yoinked/da_nsfw_checker", |
| "https://huggingface.co/spaces/LearningnRunning/adult_image_detector", |
| ], |
| "BEST": [ |
| "https://huggingface.co/spaces/Heartsync/adult", |
| "https://huggingface.co/spaces/ginigen/Flux-VIDEO", |
| "https://huggingface.co/spaces/openfree/DreamO-video", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video", |
| "https://huggingface.co/spaces/Heartsync/NSFW-novels", |
| "https://huggingface.co/spaces/fantaxy/fantasy-novel", |
| |
| ], |
| |
| "TEXT generate": [ |
| "https://huggingface.co/spaces/Heartsync/Novel-NSFW", |
| "https://huggingface.co/spaces/fantaxy/fantasy-novel", |
| "https://huggingface.co/spaces/Heartsync/NSFW-novels", |
| ], |
| |
| "TEXT TO IMAGE": [ |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo", |
| "https://huggingface.co/spaces/Heartsync/adult", |
| "https://huggingface.co/spaces/Heartsync/NSFW-novels", |
| "https://huggingface.co/spaces/IbarakiDouji/WAI-NSFW-illustrious-SDXL", |
| "https://huggingface.co/spaces/armen425221356/UnfilteredAI-NSFW-gen-v2_self_parms", |
| |
| ], |
| "IMAGE TO VIDEO": [ |
| "https://huggingface.co/spaces/Heartsync/adult", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video", |
| "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2", |
| "https://huggingface.co/spaces/openfree/DreamO-video", |
| "https://huggingface.co/spaces/Heartsync/wan2-1-fast-security", |
| "https://huggingface.co/spaces/ginigen/Flux-VIDEO", |
| "https://huggingface.co/spaces/Heartsync/WAN-VIDEO-AUDIO", |
| ], |
|
|
| "IMAGE IN/OUT-PAINTING": [ |
| "https://huggingface.co/spaces/ginigen/FLUX-Ghibli-LoRA2", |
| "https://huggingface.co/spaces/davecarrau/nsfw-face-swap", |
| "https://huggingface.co/spaces/VIDraft/ReSize-Image-Outpainting", |
| "https://huggingface.co/spaces/aiqcamp/REMOVAL-TEXT-IMAGE", |
| "https://huggingface.co/spaces/ginigen/MagicFace-V3", |
| "https://huggingface.co/spaces/openfree/ColorRevive", |
| "https://huggingface.co/spaces/ginigen/VisualCloze", |
| "https://huggingface.co/spaces/fantos/textcutobject", |
| |
| ], |
| |
| "Extension": [ |
| "https://huggingface.co/spaces/erax/EraX-NSFW-V1.0", |
| "https://huggingface.co/spaces/yoinked/da_nsfw_checker", |
| "https://huggingface.co/spaces/LearningnRunning/adult_image_detector", |
| |
| "https://huggingface.co/spaces/VIDraft/ACE-Singer", |
| "https://huggingface.co/spaces/VIDraft/Voice-Clone-Podcast", |
| "https://huggingface.co/spaces/ginigen/VoiceClone-TTS", |
| "https://huggingface.co/spaces/openfree/Multilingual-TTS", |
| "https://huggingface.co/spaces/fantaxy/Sound-AI-SFX", |
| "https://huggingface.co/spaces/ginigen/SFX-Sound-magic", |
| "https://huggingface.co/spaces/fantaxy/Remove-Video-Background", |
| "https://huggingface.co/spaces/VIDraft/stable-diffusion-3.5-large-turboX", |
|
|
| "https://huggingface.co/spaces/aiqtech/imaginpaint", |
| "https://huggingface.co/spaces/openfree/ultpixgen", |
| |
| |
| |
| ], |
| |
| "Utility": [ |
| "https://huggingface.co/spaces/openfree/Chart-GPT", |
| "https://huggingface.co/spaces/ginipick/AI-BOOK", |
| "https://huggingface.co/spaces/openfree/Live-Podcast", |
| "https://huggingface.co/spaces/openfree/AI-Podcast", |
| "https://huggingface.co/spaces/ginipick/FLUXllama", |
| "https://huggingface.co/spaces/VIDraft/Polaroid-Style", |
| "https://huggingface.co/spaces/ginigen/text3d-r1", |
| "https://huggingface.co/spaces/openfree/Naming", |
| "https://huggingface.co/spaces/ginigen/3D-LLAMA-V1", |
| "https://huggingface.co/spaces/fantaxy/flx-pulid", |
| ], |
| } |
|
|
| |
| 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): |
| """Returns (preview_url, mode)""" |
| |
| 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" |
|
|
| |
| @app.route('/api/category') |
| def api_category(): |
| cat = request.args.get('name', '') |
| urls = CATEGORIES.get(cat, []) |
| |
| |
| page = int(request.args.get('page', 1)) |
| per_page = int(request.args.get('per_page', 4)) |
| |
| 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] |
| |
| items = [ |
| { |
| "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_page |
| ] |
| |
| return jsonify({ |
| "items": items, |
| "page": page, |
| "total_pages": total_pages |
| }) |
|
|
| |
| @app.route('/') |
| 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> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>Free NSFW Hub</title> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;600&display=swap'); |
| body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb;} |
| .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px;} |
| .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer;} |
| .tab.active{background:#a78bfa;color:#1a202c;} |
| /* Updated grid to show 2x2 layout */ |
| .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;padding:0 16px 60px;max-width:1200px;margin:0 auto;} |
| @media(max-width:800px){.grid{grid-template-columns:1fr;}} |
| /* Increased card height for larger display */ |
| .card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);overflow:hidden;height:540px;display:flex;flex-direction:column;position:relative;} |
| .frame{flex:1;position:relative;overflow:hidden;} |
| .frame iframe{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0;} |
| .frame img{width:100%;height:100%;object-fit:cover;} |
| .card-label{position:absolute;top:10px;left:10px;padding:4px 8px;border-radius:4px;font-size:11px;font-weight:bold;z-index:100;text-transform:uppercase;letter-spacing:0.5px;box-shadow:0 2px 4px rgba(0,0,0,0.2);} |
| .label-live{background:linear-gradient(135deg, #00c6ff, #0072ff);color:white;} |
| .label-static{background:linear-gradient(135deg, #ff9a9e, #fad0c4);color:#333;} |
| .foot{height:44px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee;} |
| .foot a{font-size:.82rem;font-weight:700;color:#4a6dd8;text-decoration:none;} |
| .pagination{display:flex;justify-content:center;margin:20px 0;gap:10px;} |
| .pagination button{padding:5px 15px;border:none;border-radius:20px;background:#e2e8f0;cursor:pointer;} |
| .pagination button:disabled{opacity:0.5;cursor:not-allowed;} |
| </style> |
| </head> |
| <body> |
| <header style="text-align: center; padding: 20px; background: linear-gradient(135deg, #f6f8fb, #e2e8f0); border-bottom: 1px solid #ddd;"> |
| <h1 style="margin-bottom: 10px;">🔥Free NSFW Hub</h1> |
| <p style="margin-bottom: 15px; color: #666; font-size: 14px;"> |
| A curated collection of the most popular and polished NSFW Detection projects on Hugging Face Spaces,<br> |
| organized for easy visual exploration and discovery. |
| </p> |
| <p> |
| <a href="https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB" target="_blank"><img src="https://img.shields.io/static/v1?label=huggingface&message=FREE%20NSFW%20HUB&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge"></a> |
| </p> |
| |
| </header> |
| <div class="tabs" id="tabs"></div> |
| <div id="content"></div> |
| <script> |
| // Basic configuration |
| const cats = {{cats|tojson}}; |
| const tabs = document.getElementById('tabs'); |
| const content = document.getElementById('content'); |
| let active = ""; |
| let currentPage = 1; |
| // Simple utility functions |
| function makeRequest(url, method, data, callback) { |
| const xhr = new XMLHttpRequest(); |
| xhr.open(method, url, true); |
| xhr.onreadystatechange = function() { |
| if (xhr.readyState === 4 && xhr.status === 200) { |
| callback(JSON.parse(xhr.responseText)); |
| } |
| }; |
| if (method === 'POST') { |
| xhr.send(data); |
| } else { |
| xhr.send(); |
| } |
| } |
| function updateTabs() { |
| Array.from(tabs.children).forEach(b => { |
| b.classList.toggle('active', b.dataset.c === active); |
| }); |
| } |
| // Tab handler for categories |
| function loadCategory(cat, page) { |
| if(cat === active && currentPage === page) return; |
| active = cat; |
| currentPage = page || 1; |
| updateTabs(); |
| |
| content.innerHTML = '<p style="text-align:center;padding:40px">Loading…</p>'; |
| |
| makeRequest('/api/category?name=' + encodeURIComponent(cat) + '&page=' + currentPage + '&per_page=4', 'GET', null, function(data) { |
| let html = '<div class="grid">'; |
| |
| if(data.items.length === 0) { |
| html += '<p style="grid-column:1/-1;text-align:center;padding:40px">No items in this category.</p>'; |
| } else { |
| data.items.forEach(item => { |
| html += ` |
| <div class="card"> |
| <div class="card-label label-live">LIVE</div> |
| <div class="frame"> |
| <iframe src="${item.iframe}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe> |
| </div> |
| <div class="foot"> |
| <a href="${item.hf}" target="_blank">${item.title}</a> |
| </div> |
| </div> |
| `; |
| }); |
| } |
| |
| html += '</div>'; |
| |
| // Add pagination |
| html += ` |
| <div class="pagination"> |
| <button ${currentPage <= 1 ? 'disabled' : ''} onclick="loadCategory('${cat}', ${currentPage-1})">« Previous</button> |
| <span>Page ${currentPage} of ${data.total_pages}</span> |
| <button ${currentPage >= data.total_pages ? 'disabled' : ''} onclick="loadCategory('${cat}', ${currentPage+1})">Next »</button> |
| </div> |
| `; |
| |
| content.innerHTML = html; |
| }); |
| } |
| // Create category tabs |
| cats.forEach(c => { |
| const b = document.createElement('button'); |
| b.className = 'tab'; |
| b.textContent = c; |
| b.dataset.c = c; |
| b.onclick = function() { loadCategory(c, 1); }; |
| tabs.appendChild(b); |
| }); |
| // Start with the first category (Productivity) |
| loadCategory(cats[0], 1); |
| </script> |
| </body> |
| </html>''') |
| |
| |
| return render_template('index.html', cats=list(CATEGORIES.keys())) |
|
|
| if __name__ == '__main__': |
| app.run(host='0.0.0.0', port=7860) |