Testcl / app.py
Eluza133's picture
Update app.py
6d11869 verified
from flask import Flask, render_template_string, request, redirect, url_for, session, flash, jsonify
from flask_caching import Cache
import json
import os
import logging
import threading
import time
from datetime import datetime, timedelta
from huggingface_hub import HfApi, hf_hub_download
from werkzeug.utils import secure_filename
import random
app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey")
DATA_FILE = 'data_adusis.json'
REPO_ID = "Eluza133/A12d12s12" # Use your actual repo ID
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
logging.basicConfig(level=logging.INFO)
@cache.memoize(timeout=300)
def load_data():
try:
download_db_from_hf()
with open(DATA_FILE, 'r', encoding='utf-8') as file:
data = json.load(file)
if not isinstance(data, dict):
logging.warning("Data is not in dict format, initializing an empty database")
return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}}
data.setdefault('posts', [])
data.setdefault('users', {})
data.setdefault('general_chat', [])
data.setdefault('private_chats', {})
for user in data['users']:
data['users'][user].setdefault('last_chat_visit', '1970-01-01 00:00:00')
data['users'][user].setdefault('last_private_visit', '1970-01-01 00:00:00')
data['users'][user].setdefault('last_seen', '1970-01-01 00:00:00')
logging.info("Data loaded successfully")
return data
except Exception as e:
logging.error(f"Error loading data: {e}")
return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}}
def save_data(data):
try:
with open(DATA_FILE, 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
upload_db_to_hf()
cache.clear()
logging.info("Data saved and uploaded to HF")
except Exception as e:
logging.error(f"Error saving data: {e}")
raise
def upload_db_to_hf():
try:
api = HfApi()
api.upload_file(
path_or_fileobj=DATA_FILE,
path_in_repo=DATA_FILE,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
logging.info("Database uploaded to Hugging Face")
except Exception as e:
logging.error(f"Error uploading database: {e}")
def download_db_from_hf():
try:
hf_hub_download(
repo_id=REPO_ID,
filename=DATA_FILE,
repo_type="dataset",
token=HF_TOKEN_READ,
local_dir=".",
local_dir_use_symlinks=False
)
logging.info("Database downloaded from Hugging Face")
except Exception as e:
logging.error(f"Error downloading database: {e}")
if not os.path.exists(DATA_FILE):
with open(DATA_FILE, 'w', encoding='utf-8') as f:
json.dump({'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}}, f)
def periodic_backup():
while True:
upload_db_to_hf()
time.sleep(1800)
def get_unread_count(data, username):
if username not in data['users']:
return 0
last_visit = datetime.strptime(data['users'][username]['last_chat_visit'], '%Y-%m-%d %H:%M:%S')
return sum(1 for msg in data['general_chat'] if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit)
def get_private_unread_count(data, username):
if username not in data['users']:
return 0
last_visit = datetime.strptime(data['users'][username]['last_private_visit'], '%Y-%m-%d %H:%M:%S')
unread = 0
for chat_key, messages in data['private_chats'].items():
users = chat_key.split('_')
if username in users:
unread += sum(1 for msg in messages if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit and msg['sender'] != username)
return unread
def is_user_online(data, username):
if username not in data['users']:
return False
last_seen = datetime.strptime(data['users'][username]['last_seen'], '%Y-%m-%d %H:%M:%S')
return (datetime.now() - last_seen).total_seconds() < 300
def update_last_seen(data, username):
if username in data['users']:
data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_data(data)
BASE_STYLE = '''
:root {
--primary: #ff4d6d;
--secondary: #00ddeb;
--accent: #8b5cf6;
--background-light: #f5f6fa;
--background-dark: #1a1625;
--card-bg: rgba(255, 255, 255, 0.95);
--card-bg-dark: rgba(40, 35, 60, 0.95);
--text-light: #2a1e5a;
--text-dark: #e8e1ff;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
--glass-bg: rgba(255, 255, 255, 0.15);
--transition: all 0.3s ease;
--online: #34c759;
--offline: #ff3b30;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: var(--background-light);
color: var(--text-light);
line-height: 1.6;
overflow-x: hidden;
}
body.dark {
background: var(--background-dark);
color: var(--text-dark);
}
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 300px;
height: 100%;
background: var(--glass-bg);
backdrop-filter: blur(20px);
padding: 25px;
box-shadow: var(--shadow);
z-index: 1000;
transition: transform var(--transition);
}
.sidebar.hidden {
transform: translateX(-100%);
}
.sidebar-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 35px;
}
.nav-brand {
font-size: 1.8em;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.logo {
width: 40px;
height: 40px;
border-radius: 14px;
box-shadow: var(--shadow);
}
.nav-links {
display: flex;
flex-direction: column;
gap: 15px;
}
.nav-link {
display: flex;
align-items: center;
gap: 15px;
padding: 14px 25px;
background: var(--card-bg);
color: var(--text-light);
text-decoration: none;
border-radius: 14px;
font-size: 1.1em;
font-weight: 600;
transition: var(--transition);
position: relative;
}
body.dark .nav-link {
background: var(--card-bg-dark);
color: var(--text-dark);
}
.nav-link:hover {
transform: translateX(5px);
background: var(--primary);
color: white;
box-shadow: 0 6px 20px rgba(255, 77, 109, 0.4);
}
.nav-link .badge {
position: absolute;
right: 15px;
background: var(--secondary);
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.8em;
font-weight: 700;
}
.logout-btn {
background: var(--secondary);
color: white;
}
.logout-btn:hover {
background: #00b8c5;
}
.menu-btn {
display: none;
font-size: 28px;
background: var(--glass-bg);
border: none;
color: var(--primary);
cursor: pointer;
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
padding: 12px;
border-radius: 50%;
box-shadow: var(--shadow);
transition: var(--transition);
}
.menu-btn:hover {
background: var(--primary);
color: white;
}
.container {
margin: 20px auto 20px 320px;
max-width: 1200px;
padding: 25px;
transition: var(--transition);
}
.btn {
padding: 14px 28px;
background: var(--primary);
color: white;
border: none;
border-radius: 14px;
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
transition: var(--transition);
display: inline-flex;
align-items: center;
gap: 10px;
box-shadow: var(--shadow);
}
.btn:hover {
transform: scale(1.05);
background: #e6415f;
box-shadow: 0 8px 25px rgba(255, 77, 109, 0.5);
}
input, textarea, select {
width: 100%;
padding: 14px;
margin: 12px 0;
border: none;
border-radius: 14px;
background: var(--glass-bg);
color: var(--text-light);
font-size: 1.1em;
transition: var(--transition);
box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.1);
}
body.dark input, body.dark textarea, body.dark select {
color: var(--text-dark);
}
input:focus, textarea:focus, select:focus {
outline: none;
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 0 4px var(--primary);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
z-index: 2000;
justify-content: center;
align-items: center;
transition: opacity var(--transition);
}
.modal img, .modal video {
max-width: 95%;
max-height: 95%;
object-fit: contain;
border-radius: 20px;
box-shadow: var(--shadow);
animation: zoomIn 0.3s ease;
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--glass-bg);
border: none;
padding: 12px;
border-radius: 50%;
cursor: pointer;
font-size: 24px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.theme-toggle:hover {
transform: rotate(90deg);
background: var(--accent);
color: white;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-left: 8px;
}
.online { background: var(--online); }
.offline { background: var(--offline); }
@keyframes zoomIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
@media (max-width: 900px) {
.sidebar {
width: 100%;
max-width: 300px;
transform: translateX(-100%);
}
.sidebar.active {
transform: translateX(0);
}
.menu-btn {
display: block;
}
.container {
margin: 80px 20px 20px 20px;
max-width: calc(100% - 40px);
}
.theme-toggle {
top: 80px;
}
}
@media (max-width: 480px) {
.nav-brand { font-size: 1.5em; }
.nav-link { font-size: 1em; padding: 12px 20px; }
.btn { padding: 12px 20px; font-size: 1em; }
}
'''
NAV_HTML = '''
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/673b00f35373479538ac373c/W_dumUND8K6IlMxVmpUgS.jpeg" alt="Logo" class="logo">
<span class="nav-brand">ADUSIS QoSHUB</span>
</div>
<nav class="nav-links">
<a href="{{ url_for('feed') }}" class="nav-link"><span>📜</span> Feed</a>
{% if is_authenticated %}
<a href="{{ url_for('profile') }}" class="nav-link"><span>👤</span> Profile ({{ username }}) <span class="status-dot {{ 'online' if is_online else 'offline' }}"></span></a>
<a href="{{ url_for('upload') }}" class="nav-link"><span>⬆️</span> Upload</a>
<a href="{{ url_for('chat') }}" class="nav-link"><span>💬</span> Chat {% if unread_count > 0 %}<span class="badge">{{ unread_count }}</span>{% endif %}</a>
<a href="{{ url_for('users') }}" class="nav-link"><span>👥</span> Users <span class="badge">{{ user_count }}</span></a>
<a href="{{ url_for('messages') }}" class="nav-link"><span>✉️</span> Messages {% if private_unread_count > 0 %}<span class="badge">{{ private_unread_count }}</span>{% endif %}</a>
<a href="{{ url_for('logout') }}" class="nav-link logout-btn"><span>🚪</span> Logout</a>
{% else %}
<a href="{{ url_for('login') }}" class="nav-link"><span>🔑</span> Login</a>
<a href="{{ url_for('register') }}" class="nav-link"><span>✨</span> Register</a>
<a href="{{ url_for('chat') }}" class="nav-link"><span>💬</span> Chat (View Only)</a>
<a href="{{ url_for('users') }}" class="nav-link"><span>👥</span> Users <span class="badge">{{ user_count }}</span></a>
{% endif %}
</nav>
</aside>
'''
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
data = load_data()
if username in data['users']:
flash('User already exists!')
return redirect(url_for('register'))
data['users'][username] = {
'password': password,
'bio': '',
'link': '',
'avatar': None,
'last_chat_visit': '1970-01-01 00:00:00',
'last_private_visit': '1970-01-01 00:00:00',
'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
save_data(data)
flash('Registration successful! Please login.')
return redirect(url_for('login'))
is_authenticated = 'username' in session
username = session.get('username', None)
data = load_data()
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<meta name="google-site-verification" content="V6EqDTA9Oj9V1OfNbnBHj5RKrdcXlABD8tqfEFVUHJY" />
<title>Register - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 500px;
background: var(--card-bg);
padding: 35px;
border-radius: 20px;
box-shadow: var(--shadow);
animation: slideUp 0.4s ease;
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2em;
font-weight: 800;
text-align: center;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.flash {
color: var(--secondary);
text-align: center;
margin-bottom: 15px;
font-size: 1em;
font-weight: 600;
}
.link {
text-align: center;
margin-top: 20px;
color: var(--primary);
font-size: 1em;
text-decoration: none;
font-weight: 600;
transition: var(--transition);
}
.link:hover {
color: var(--accent);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>Register</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit" class="btn">Register</button>
</form>
<p class="link"><a href="{{ url_for('login') }}">Already have an account? Login</a></p>
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html, is_authenticated=is_authenticated, username=username, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online)
@app.route('/login', methods=['GET', 'POST'])
def login():
data = load_data()
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username in data['users'] and data['users'][username]['password'] == password:
session['username'] = username
session.permanent = True
update_last_seen(data, username)
return redirect(url_for('feed'))
flash('Invalid username or password!')
return redirect(url_for('login'))
is_authenticated = 'username' in session
username = session.get('username', None)
if is_authenticated:
update_last_seen(data, username)
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Login - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 500px;
background: var(--card-bg);
padding: 35px;
border-radius: 20px;
box-shadow: var(--shadow);
animation: slideUp 0.4s ease;
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2em;
font-weight: 800;
text-align: center;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.flash {
color: var(--secondary);
text-align: center;
margin-bottom: 15px;
font-size: 1em;
font-weight: 600;
}
.link {
text-align: center;
margin-top: 20px;
color: var(--primary);
font-size: 1em;
text-decoration: none;
font-weight: 600;
transition: var(--transition);
}
.link:hover {
color: var(--accent);
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>Login</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit" class="btn">Login</button>
</form>
<p class="link"><a href="{{ url_for('register') }}">Don't have an account? Register</a></p>
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html, is_authenticated=is_authenticated, username=username, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online)
@app.route('/logout')
def logout():
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
session.pop('username', None)
return redirect(url_for('feed'))
@app.route('/toggle_like/<post_id>', methods=['POST'])
def toggle_like(post_id):
if 'username' not in session:
return jsonify({'error': 'Not authenticated'}), 401
data = load_data()
username = session['username']
post = next((p for p in data['posts'] if p['id'] == post_id), None)
if not post:
return jsonify({'error': 'Post not found'}), 404
likes = post.get('likes', [])
if username in likes:
post['likes'] = [u for u in likes if u != username]
liked = False
else:
post['likes'] = likes + [username]
liked = True
save_data(data)
return jsonify({'liked': liked, 'likes_count': len(post['likes'])})
@app.route('/increment_view/<post_id>', methods=['POST'])
def increment_view(post_id):
data = load_data()
post = next((p for p in data['posts'] if p['id'] == post_id), None)
if post:
post['views'] = post.get('views', 0) + 1
save_data(data)
return jsonify({'views': post['views']})
return jsonify({'error': 'Post not found'}), 404
@app.route('/', methods=['GET', 'POST'])
def feed():
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
posts = sorted(data.get('posts', []), key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
search_query = request.args.get('search', '').strip().lower() # Get search query from URL parameters
if search_query:
posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()]
if request.method == 'POST' and is_authenticated:
post_id = request.form.get('post_id')
post = next((p for p in data['posts'] if p['id'] == post_id), None)
if not post:
return "Post not found", 404
if 'comment' in request.form:
comment_text = request.form.get('comment')
if comment_text:
post['comments'] = post.get('comments', []) + [{
'user': username,
'text': comment_text,
'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}]
# Removed share handling
save_data(data)
return redirect(url_for('feed', search=search_query)) # Maintain search query
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<meta name="google-site-verification" content="V6EqDTA9Oj9V1OfNbnBHj5RKrdcXlABD8tqfEFVUHJY" />
<title>Adusis - QoS, BBC, BNWO HUB</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
body, html {
height: 100%;
margin: 0;
padding: 0;
}
.feed-container {
height: calc(100vh - 80px); /* Adjust for bottom navbar */
width: 100%;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
margin-left: 0;
padding: 0;
-webkit-overflow-scrolling: touch;
padding-bottom: 80px; /* Space for the bottom navbar */
}
.post-container {
height: calc(100vh - 80px); /* Adjust for bottom navbar */
width: 100%;
position: relative;
scroll-snap-align: center;
display: flex;
align-items: center;
justify-content: center;
background: var(--background-light);
overflow: hidden;
margin-bottom: 10px;
}
body.dark .post-container {
background: var(--background-dark);
}
.post-media {
max-height: 100vh;
max-width: 100%;
object-fit: contain;
border-radius: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
loading: lazy;
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.8);
border: none;
border-radius: 50%;
width: 80px;
height: 80px;
font-size: 40px;
color: var(--primary);
cursor: pointer;
z-index: 2;
transition: var(--transition);
}
.play-btn:hover {
background: var(--primary);
color: white;
}
.post-overlay {
position: absolute;
bottom: 80px;
left: 25px;
right: 25px;
z-index: 2;
color: white;
background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
padding: 20px;
border-radius: 14px;
pointer-events: none;
}
.post-overlay h2 {
font-size: 1.5em;
font-weight: 800;
margin-bottom: 10px;
text-shadow: 0 3px 6px rgba(0,0,0,0.9);
}
.post-overlay p {
font-size: 1em;
text-shadow: 0 3px 6px rgba(0,0,0,0.9);
}
.username-link {
color: var(--primary);
font-weight: 700;
text-decoration: none;
}
.username-link:hover {
color: var(--accent);
}
.post-actions {
position: absolute;
right: 25px;
bottom: 100px;
display: flex;
flex-direction: column;
gap: 25px;
z-index: 3;
}
.action-btn {
background: rgba(255, 255, 255, 0.25);
border: none;
border-radius: 50%;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition);
color: white;
font-size: 28px;
backdrop-filter: blur(10px);
box-shadow: var(--shadow);
}
.action-btn:hover {
background: var(--primary);
transform: scale(1.1);
}
.action-btn.liked {
color: var(--primary);
background: rgba(255, 77, 109, 0.5);
}
.action-count {
color: white;
text-align: center;
font-size: 16px;
margin-top: 8px;
text-shadow: 0 3px 6px rgba(0,0,0,0.9);
}
.comment-section {
display: none;
position: absolute;
bottom: 180px;
left: 25px;
right: 25px;
background: rgba(0, 0, 0, 0.85);
padding: 20px;
border-radius: 14px;
max-height: 60vh;
overflow-y: auto;
z-index: 4;
box-shadow: var(--shadow);
}
.comment-section.active {
display: block;
}
.comment {
margin-bottom: 12px;
font-size: 14px;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.8);
}
.comment-form {
display: flex;
gap: 12px;
margin-top: 12px;
}
.comment-form textarea {
flex-grow: 1;
height: 45px;
background: rgba(255, 255, 255, 0.25);
color: white;
border: none;
border-radius: 10px;
padding: 12px;
font-size: 14px;
resize: none;
}
.comment-form .btn {
padding: 12px 20px;
font-size: 14px;
}
.share-options {
display: none; /* Initially hide share options */
position: absolute;
bottom: 280px;
right: 25px;
background: rgba(0, 0, 0, 0.85);
padding: 15px;
border-radius: 14px;
z-index: 4;
box-shadow: var(--shadow);
}
/* .share-options.active {
display: block;
}
*/
.share-option {
background: none;
border: none;
color: white;
padding: 10px 15px;
cursor: pointer;
width: 100%;
text-align: left;
font-size: 14px;
transition: var(--transition);
}
.share-option:hover {
background: var(--primary);
border-radius: 10px;
}
/* Bottom Navigation Bar Styles */
.bottom-navbar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: var(--card-bg);
display: flex;
justify-content: space-around;
align-items: center;
padding: 10px 0;
z-index: 1000;
box-shadow: 0 -5px 15px rgba(0,0,0,0.1);
}
body.dark .bottom-navbar{
background: var(--card-bg-dark);
}
.bottom-navbar a, .bottom-navbar button {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none;
color: var(--text-light);
font-size: 14px;
padding: 8px 12px;
border-radius: 10px;
transition: var(--transition)
}
body.dark .bottom-navbar a, body.dark .bottom-navbar button{
color: var(--text-dark);
}
.bottom-navbar a:hover, .bottom-navbar button:hover {
background-color: var(--primary);
color: white;
}
.bottom-navbar i {
font-size: 24px;
margin-bottom: 4px;
}
.search-bar-container {
position: fixed; /* Fixed position */
bottom: 20px; /* Distance from the bottom */
left: 50%; /* Center horizontally */
transform: translateX(-50%); /* Adjust for centering */
width: 80%; /* Or any desired width */
max-width: 500px; /* Max width */
z-index: 1001; /* Ensure it's above other content, but below sidebar */
}
.search-bar {
width: 100%;
padding: 10px 15px;
border-radius: 20px;
border: none;
background-color: rgba(255,255,255,0.9);
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
font-size: 16px;
}
@media (max-width: 480px) {
.post-overlay {
bottom: 60px;
left: 15px;
right: 15px;
padding: 15px;
}
.post-actions {
bottom: 80px;
right: 15px;
gap: 20px;
}
.action-btn {
width: 50px;
height: 50px;
font-size: 24px;
}
.action-count {
font-size: 14px;
}
.comment-section {
bottom: 150px;
left: 15px;
right: 15px;
}
.post-overlay h2 {
font-size: 1.2em;
}
.post-overlay p {
font-size: 0.9em;
}
.play-btn {
width: 60px;
height: 60px;
font-size: 30px;
}
.bottom-navbar {
padding: 8px 0;
}
.bottom-navbar a, .bottom-navbar button {
font-size: 12px;
padding: 6px 10px;
}
.bottom-navbar i {
font-size: 20px;
}
.search-bar-container{
bottom:10px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<!-- Search Bar -->
<div class="search-bar-container">
<form action="{{ url_for('feed') }}" method="GET">
<input type="text" name="search" class="search-bar" placeholder="Search posts..." value="{{ request.args.get('search', '') }}">
</form>
</div>
<div class="feed-container" id="feed">
{% for post in posts %}
<div class="post-container" id="post-{{ post['id'] }}">
{% if post['type'] == 'video' %}
<video class="post-media" preload="metadata" muted loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
</video>
<button class="play-btn" onclick="playVideo('{{ post['id'] }}')">▶</button>
{% else %}
<img class="post-media" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}">
{% endif %}
<div class="post-overlay">
<h2>{{ post['title'] }}</h2>
<p>{{ post['description']|truncate(150) }}</p>
<p>By: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> <span class="status-dot {{ 'online' if is_user_online(post['uploader']) else 'offline' }}"></span></p>
</div>
<div class="post-actions">
<button class="action-btn {% if username in post['likes'] %}liked{% endif %}" onclick="toggleLike('{{ post['id'] }}')">
</button>
<span class="action-count" id="likes-{{ post['id'] }}">{{ post['likes']|length }}</span>
<button class="action-btn" onclick="toggleComments('{{ post['id'] }}')">💬</button>
<span class="action-count">{{ post['comments']|length }}</span>
<button class="action-btn" onclick="copyLink('{{ url_for('post_page', post_id=post['id'], _external=True) }}')">➤</button>
<span class="action-count" id="views-{{ post['id'] }}">{{ post['views'] }}</span>
</div>
<div class="comment-section" id="comments-{{ post['id'] }}">
{% for comment in post.get('comments', []) %}
<div class="comment">
<strong><a href="{{ url_for('user_profile', username=comment['user']) }}" class="username-link">{{ comment['user'] }}</a>:</strong> {{ comment['text'] }}
</div>
{% endfor %}
{% if is_authenticated %}
<form method="POST" class="comment-form" onsubmit="return submitComment(event, '{{ post['id'] }}')">
<input type="hidden" name="post_id" value="{{ post['id'] }}">
<textarea name="comment" placeholder="Add a comment"></textarea>
<button type="submit" class="btn">Send</button>
</form>
{% endif %}
</div>
<div class="share-options" id="share-{{ post['id'] }}">
</div>
</div>
{% endfor %}
</div>
<!-- Bottom Navigation Bar -->
<div class="bottom-navbar">
<a href="{{ url_for('feed') }}"><i class="fas fa-home"></i> Home</a>
{% if is_authenticated %}
<a href="{{ url_for('upload') }}"><i class="fas fa-upload"></i> Upload</a>
<a href="{{ url_for('profile') }}"><i class="fas fa-user"></i> Profile</a>
{% endif %}
<a href="#"><i class="fas fa-search"></i> Search</a>
</div>
<script src="https://kit.fontawesome.com/your-font-awesome-kit-id.js" crossorigin="anonymous"></script>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function toggleComments(postId) {
const comments = document.getElementById('comments-' + postId);
comments.classList.toggle('active');
}
/* function toggleShare(postId) {
const shareOptions = document.getElementById('share-' + postId);
shareOptions.classList.toggle('active');
}
*/
function toggleLike(postId) {
fetch('/toggle_like/' + postId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
const btn = document.querySelector(`#post-${postId} .action-btn:first-child`);
const count = document.getElementById('likes-' + postId);
btn.classList.toggle('liked', data.liked);
count.textContent = data.likes_count;
})
.catch(error => console.error('Error:', error));
}
function submitComment(event, postId) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
fetch('/', {
method: 'POST',
body: formData
}).then(() => {
window.location.reload();
});
return false;
}
function copyLink(url) {
navigator.clipboard.writeText(url).then(() => {
alert('Link copied to clipboard!');
});
}
function playVideo(postId) {
const video = document.querySelector(`#post-${postId} .post-media`);
const playBtn = document.querySelector(`#post-${postId} .play-btn`);
if (video.paused) {
video.play();
playBtn.style.display = 'none';
} else {
video.pause();
playBtn.style.display = 'block';
}
video.onended = () => {
playBtn.style.display = 'block';
};
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
const feed = document.getElementById('feed');
const medias = document.querySelectorAll('.post-media');
// Intersection Observer for View Tracking and Video Playback
medias.forEach(media => {
const postId = media.parentElement.id.split('-')[1];
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting && media.tagName === 'VIDEO') {
media.pause();
const playBtn = document.querySelector(`#post-${postId} .play-btn`);
if (playBtn) playBtn.style.display = 'block';
}
if (entry.isIntersecting) {
fetch('/increment_view/' + postId, { method: 'POST' })
.then(response => response.json())
.then(data => {
document.getElementById('views-' + postId).textContent = data.views;
});
}
});
}, { threshold: 0.7 });
observer.observe(media);
});
let lastScrollTop = 0;
feed.addEventListener('scroll', () => {
const scrollTop = feed.scrollTop;
const scrollHeight = feed.scrollHeight;
const clientHeight = feed.clientHeight;
const posts = document.querySelectorAll('.post-container');
// Auto-scrolling to the center of the current post
posts.forEach(post => {
const rect = post.getBoundingClientRect();
if (rect.top >= 0 && rect.bottom <= clientHeight) {
post.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
});
lastScrollTop = scrollTop;
}, { passive: true });
};
</script>
</body>
</html>
'''
return render_template_string(html,
posts=posts,
is_authenticated=is_authenticated,
username=username,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online,
is_user_online=lambda u: is_user_online(data, u),
data=data)
@app.route('/post/<post_id>', methods=['GET', 'POST'])
def post_page(post_id):
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
post = next((p for p in data['posts'] if p['id'] == post_id), None)
if not post:
return "Post not found", 404
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
post['views'] = post.get('views', 0) + 1
save_data(data)
if request.method == 'POST' and is_authenticated:
if 'comment' in request.form:
comment_text = request.form.get('comment')
if comment_text:
post['comments'] = post.get('comments', []) + [{'user': username, 'text': comment_text, 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]
save_data(data)
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>{{ post['title'] }} - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 900px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2.2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
video, img {
width: 100%;
max-height: 500px;
object-fit: cover;
border-radius: 16px;
box-shadow: var(--shadow);
margin-bottom: 25px;
loading: lazy;
}
p {
font-size: 1.1em;
margin-bottom: 15px;
}
.like-btn.liked {
background: var(--secondary);
}
.like-btn.liked:hover {
background: #00b8c5;
}
.comment-section {
margin-top: 30px;
}
.comment {
background: var(--glass-bg);
padding: 15px;
border-radius: 14px;
margin-bottom: 15px;
font-size: 1em;
}
.username-link {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.username-link:hover {
color: var(--accent);
}
@media (max-width: 480px) {
video, img {
max-height: 350px;
}
h1 {
font-size: 1.8em;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>{{ post['title'] }}</h1>
{% if post['type'] == 'video' %}
<video controls preload="metadata" loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
</video>
{% else %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" loading="lazy" onclick="openModal(this.src)">
{% endif %}
<p>{{ post['description'] }}</p>
<p>By: <a href="{{ url_for('user_profile', username=post['uploader']) }}" class="username-link">{{ post['uploader'] }}</a> <span class="status-dot {{ 'online' if is_user_online(post['uploader']) else 'offline' }}"></span> | {{ post['upload_date'] }}</p>
<p>Views: <span id="views-{{ post['id'] }}">{{ post['views'] }}</span> | Likes: <span id="likes-{{ post['id'] }}">{{ post['likes']|length }}</span></p>
{% if is_authenticated %}
<button class="btn like-btn {% if username in post['likes'] %}liked{% endif %}" onclick="toggleLike('{{ post['id'] }}')">
{% if username in post['likes'] %}Unlike{% else %}Like{% endif %}
</button>
<form method="POST" class="comment-section">
<textarea name="comment" placeholder="Leave a comment" rows="3"></textarea>
<button type="submit" class="btn">Submit</button>
</form>
{% endif %}
<h3 style="margin-top: 30px; font-size: 1.5em;">Comments</h3>
{% for comment in post.get('comments', []) %}
<div class="comment">
<p><strong><a href="{{ url_for('user_profile', username=comment['user']) }}" class="username-link">{{ comment['user'] }}</a> <span class="status-dot {{ 'online' if is_user_online(comment['user']) else 'offline' }}"></span></strong> ({{ comment['date'] }}): {{ comment['text'] }}</p>
</div>
{% endfor %}
<a href="{{ url_for('feed') }}" class="btn">Back to Feed</a>
{% if not is_authenticated %}
<p style="margin-top: 15px;"><a href="{{ url_for('login') }}">Login</a> to like and comment.</p>
{% endif %}
</div>
<div class="modal" id="imageModal" onclick="closeModal(event)">
<img id="modalImage" src="">
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function openModal(src) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'flex';
modalImg.src = src;
}
function closeModal(event) {
if (event.target.tagName !== 'IMG') {
document.getElementById('imageModal').style.display = 'none';
}
}
function toggleLike(postId) {
fetch('/toggle_like/' + postId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
const btn = document.querySelector('.like-btn');
btn.classList.toggle('liked', data.liked);
btn.textContent = data.liked ? 'Unlike' : 'Like';
document.getElementById('likes-' + postId).textContent = data.likes_count;
})
.catch(error => console.error('Error:', error));
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online, is_user_online=lambda u: is_user_online(data, u))
@app.route('/profile', methods=['GET', 'POST'])
def profile():
if 'username' not in session:
flash('Login to view your profile!')
return redirect(url_for('login'))
data = load_data()
username = session['username']
update_last_seen(data, username)
user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username)
private_unread_count = get_private_unread_count(data, username)
user_count = len(data['users'])
is_online = is_user_online(data, username)
total_views = sum(post.get('views', 0) for post in user_posts)
total_likes = sum(len(post.get('likes', [])) for post in user_posts)
user_data = data['users'].get(username, {})
bio = user_data.get('bio', '')
link = user_data.get('link', '')
avatar = user_data.get('avatar', None)
last_seen = user_data.get('last_seen', 'Never')
if request.method == 'POST':
if 'delete_post' in request.form:
post_id = request.form.get('post_id')
if post_id:
data['posts'] = [p for p in data['posts'] if p['id'] != post_id or p['uploader'] != username]
save_data(data)
return redirect(url_for('profile'))
elif 'update_profile' in request.form:
bio = request.form.get('bio', '')
link = request.form.get('link', '')
avatar_file = request.files.get('avatar')
if avatar_file and avatar_file.filename:
filename = secure_filename(avatar_file.filename)
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
avatar_file.save(temp_path)
api = HfApi()
avatar_path = f"avatars/{username}/{filename}"
api.upload_file(
path_or_fileobj=temp_path,
path_in_repo=avatar_path,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Uploaded avatar for {username}"
)
data['users'][username]['avatar'] = avatar_path
if os.path.exists(temp_path):
os.remove(temp_path)
data['users'][username]['bio'] = bio
data['users'][username]['link'] = link
save_data(data)
return redirect(url_for('profile'))
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Profile - {{ username }} - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 1000px;
}
.profile-header {
display: flex;
gap: 25px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
margin-bottom: 30px;
}
body.dark .profile-header {
background: var(--card-bg-dark);
}
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
transition: var(--transition);
loading: lazy;
}
.avatar:hover {
transform: scale(1.05);
}
.profile-info h1 {
font-size: 2.2em;
font-weight: 800;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
margin-bottom: 15px;
}
.profile-info p {
font-size: 1.1em;
margin-bottom: 10px;
}
.post-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 25px;
}
.post-item {
background: var(--card-bg);
padding: 20px;
border-radius: 20px;
box-shadow: var(--shadow);
transition: var(-- transition);
}
body.dark .post-item {
background: var(--card-bg-dark);
}
.post-item:hover {
transform: translateY(-8px);
}
.post-preview {
width: 100%;
height: 220px;
object-fit: cover;
border-radius: 16px;
margin-bottom: 15px;
loading: lazy;
}
.post-item h3 {
font-size: 1.4em;
font-weight: 600;
margin-bottom: 10px;
}
.delete-btn {
background: var(--secondary);
}
.delete-btn:hover {
background: #00b8c5;
}
.username-link {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.username-link:hover {
color: var(--accent);
}
@media (max-width: 480px) {
.profile-header {
flex-direction: column;
align-items: center;
padding: 20px;
}
.avatar {
width: 100px;
height: 100px;
}
.profile-info h1 {
font-size: 1.8em;
text-align: center;
}
.post-grid {
grid-template-columns: 1fr;
}
.post-preview {
height: 180px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<div class="profile-header">
{% if avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Avatar" class="avatar" loading="lazy" onclick="openModal(this.src)">
{% else %}
<div class="avatar" style="background: var(--primary);"></div>
{% endif %}
<div class="profile-info">
<h1>{{ username }} <span class="status-dot {{ 'online' if is_online else 'offline' }}"></span></h1>
<p>{{ bio }}</p>
{% if link %}
<p><a href="{{ link }}" target="_blank" class="username-link">{{ link }}</a></p>
{% endif %}
<p>Total Views: {{ total_views }} | Total Likes: {{ total_likes }}</p>
<p>Last Seen: {{ last_seen }}</p>
</div>
</div>
<h2 style="font-size: 1.8em; margin-bottom: 20px;">Edit Profile</h2>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="update_profile" value="1">
<textarea name="bio" placeholder="Bio">{{ bio }}</textarea>
<input type="text" name="link" placeholder="Link" value="{{ link }}">
<input type="file" name="avatar" accept="image/*">
<button type="submit" class="btn">Update Profile</button>
</form>
<h2 style="font-size: 1.8em; margin: 30px 0 20px;">My Posts</h2>
<div class="post-grid">
{% for post in user_posts %}
<div class="post-item">
<a href="{{ url_for('post_page', post_id=post['id']) }}">
{% if post['type'] == 'video' %}
<video class="post-preview" preload="metadata" muted loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
</video>
{% else %}
<img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" loading="lazy">
{% endif %}
<h3>{{ post['title'] }}</h3>
</a>
<p>{{ post['description']|truncate(100) }}</p>
<p>{{ post['upload_date'] }} | Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
<form method="POST" style="margin-top: 15px;">
<input type="hidden" name="post_id" value="{{ post['id'] }}">
<button type="submit" name="delete_post" class="btn delete-btn">Delete</button>
</form>
</div>
{% endfor %}
{% if not user_posts %}
<p style="font-size: 1.1em;">You haven't uploaded any posts yet.</p>
{% endif %}
</div>
</div>
<div class="modal" id="imageModal" onclick="closeModal(event)">
<img id="modalImage" src="">
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function openModal(src) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'flex';
modalImg.src = src;
}
function closeModal(event) {
if (event.target.tagName !== 'IMG') {
document.getElementById('imageModal').style.display = 'none';
}
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
const videos = document.querySelectorAll('.post-preview');
videos.forEach(video => {
if (video.tagName === 'VIDEO') {
video.addEventListener('loadedmetadata', () => {
video.currentTime = Math.random() * video.duration;
});
}
});
};
</script>
</body>
</html>
'''
return render_template_string(html,
username=username,
user_posts=user_posts,
bio=bio,
link=link,
avatar=avatar,
total_views=total_views,
total_likes=total_likes,
last_seen=last_seen,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online)
@app.route('/user/<username>', methods=['GET'])
def user_profile(username):
data = load_data()
current_user = session.get('username', None)
if current_user:
update_last_seen(data, current_user)
if username not in data['users']:
return "User not found", 404
user_posts = sorted([p for p in data['posts'] if p['uploader'] == username], key=lambda x: datetime.strptime(x['upload_date'], '%Y-%m-%d %H:%M:%S'), reverse=True)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, current_user) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, current_user) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, current_user) if is_authenticated else False
total_views = sum(post.get('views', 0) for post in user_posts)
total_likes = sum(len(post.get('likes', [])) for post in user_posts)
user_data = data['users'].get(username, {})
bio = user_data.get('bio', '')
link = user_data.get('link', '')
avatar = user_data.get('avatar', None)
last_seen = user_data.get('last_seen', 'Never')
user_online = is_user_online(data, username)
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>{{ username }}'s Profile - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 1000px;
}
.profile-header {
display: flex;
gap: 25px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
margin-bottom: 30px;
}
body.dark .profile-header {
background: var(--card-bg-dark);
}
.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
transition: var(--transition);
loading: lazy;
}
.avatar:hover {
transform: scale(1.05);
}
.profile-info h1 {
font-size: 2.2em;
font-weight: 800;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
margin-bottom: 15px;
}
.profile-info p {
font-size: 1.1em;
margin-bottom: 10px;
}
.post-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 25px;
}
.post-item {
background: var(--card-bg);
padding: 20px;
border-radius: 20px;
box-shadow: var(--shadow);
transition: var(--transition);
}
body.dark .post-item {
background: var(--card-bg-dark);
}
.post-item:hover {
transform: translateY(-8px);
}
.post-preview {
width: 100%;
height: 220px;
object-fit: cover;
border-radius: 16px;
margin-bottom: 15px;
loading: lazy;
}
.post-item h3 {
font-size: 1.4em;
font-weight: 600;
margin-bottom: 10px;
}
.username-link {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.username-link:hover {
color: var(--accent);
}
@media (max-width: 480px) {
.profile-header {
flex-direction: column;
align-items: center;
padding: 20px;
}
.avatar {
width: 100px;
height: 100px;
}
.profile-info h1 {
font-size: 1.8em;
text-align: center;
}
.post-grid {
grid-template-columns: 1fr;
}
.post-preview {
height: 180px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<div class="profile-header">
{% if avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Avatar" class="avatar" loading="lazy" onclick="openModal(this.src)">
{% else %}
<div class="avatar" style="background: var(--primary);"></div>
{% endif %}
<div class="profile-info">
<h1>{{ username }} <span class="status-dot {{ 'online' if user_online else 'offline' }}"></span></h1>
<p>{{ bio }}</p>
{% if link %}
<p><a href="{{ link }}" target="_blank" class="username-link">{{ link }}</a></p>
{% endif %}
<p>Total Views: {{ total_views }} | Total Likes: {{ total_likes }}</p>
<p>Last Seen: {{ last_seen }}</p>
{% if is_authenticated and current_user != username %}
<a href="{{ url_for('private_chat', recipient=username) }}" class="btn">Send Message</a>
{% endif %}
</div>
</div>
<h2 style="font-size: 1.8em; margin-bottom: 20px;">Posts</h2>
<div class="post-grid">
{% for post in user_posts %}
<div class="post-item">
<a href="{{ url_for('post_page', post_id=post['id']) }}">
{% if post['type'] == 'video' %}
<video class="post-preview" preload="metadata" muted loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" type="video/mp4">
</video>
{% else %}
<img class="post-preview" src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }}" alt="{{ post['title'] }}" loading="lazy">
{% endif %}
<h3>{{ post['title'] }}</h3>
</a>
<p>{{ post['description']|truncate(100) }}</p>
<p>{{ post['upload_date'] }} | Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}</p>
</div>
{% endfor %}
{% if not user_posts %}
<p style="font-size: 1.1em;">This user hasn't uploaded any posts yet.</p>
{% endif %}
</div>
</div>
<div class="modal" id="imageModal" onclick="closeModal(event)">
<img id="modalImage" src="">
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function openModal(src) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'flex';
modalImg.src = src;
}
function closeModal(event) {
if (event.target.tagName !== 'IMG') {
document.getElementById('imageModal').style.display = 'none';
}
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
const videos = document.querySelectorAll('.post-preview');
videos.forEach(video => {
if (video.tagName === 'VIDEO') {
video.addEventListener('loadedmetadata', () => {
video.currentTime = Math.random() * video.duration;
});
}
});
};
</script>
</body>
</html>
'''
return render_template_string(html,
username=username,
user_posts=user_posts,
bio=bio,
link=link,
avatar=avatar,
total_views=total_views,
total_likes=total_likes,
last_seen=last_seen,
is_authenticated=is_authenticated,
current_user=current_user,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online,
user_online=user_online)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if 'username' not in session:
flash('Login to upload content!')
return redirect(url_for('login'))
data = load_data()
username = session['username']
update_last_seen(data, username)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username)
private_unread_count = get_private_unread_count(data, username)
user_count = len(data['users'])
is_online = is_user_online(data, username)
if request.method == 'POST':
title = request.form.get('title')
description = request.form.get('description')
file = request.files.get('file')
if not file or not title:
flash('Title and file are required!')
return redirect(url_for('upload'))
filename = secure_filename(file.filename)
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(temp_path)
file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
api = HfApi()
file_path = f"{file_type}s/{username}/{filename}"
api.upload_file(
path_or_fileobj=temp_path,
path_in_repo=file_path,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Uploaded {file_type} by {username}"
)
post_id = str(random.randint(100000, 999999))
while any(p['id'] == post_id for p in data['posts']):
post_id = str(random.randint(100000, 999999))
data['posts'].append({
'id': post_id,
'title': title,
'description': description,
'filename': f"{username}/{filename}",
'type': file_type,
'uploader': username,
'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'views': 0,
'likes': [],
'comments': []
})
save_data(data)
if os.path.exists(temp_path):
os.remove(temp_path)
return redirect(url_for('profile'))
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Upload Content - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 700px;
background: var(--card-bg);
padding: 35px;
border-radius: 20px;
box-shadow: var(--shadow);
animation: slideUp 0.4s ease;
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
#progress-container {
width: 100%;
height: 6px;
background: var(--glass-bg);
border-radius: 6px;
margin-top: 20px;
overflow: hidden;
}
#progress-bar {
width: 0%;
height: 100%;
background: var(--primary);
transition: width 0.3s ease;
}
@media (max-width: 480px) {
.container {
padding: 20px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>Upload Content</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="flash" style="color: var(--secondary); text-align: center; margin-bottom: 15px;">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form id="upload-form" enctype="multipart/form-data">
<input type="text" name="title" placeholder="Title" required>
<textarea name="description" placeholder="Description" rows="4"></textarea>
<input type="file" name="file" accept="video/*,image/*" required>
<button type="submit" class="btn">Upload</button>
</form>
<div id="progress-container">
<div id="progress-bar"></div>
</div>
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
document.getElementById('upload-form').onsubmit = async function(e) {
e.preventDefault();
const formData = new FormData(this);
const progressBar = document.getElementById('progress-bar');
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
progressBar.style.width = percent + '%';
}
};
xhr.onload = function() {
if (xhr.status === 200) {
window.location = '/profile';
} else {
alert('Upload error');
}
};
xhr.send(formData);
};
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html,
username=username,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online)
@app.route('/chat', methods=['GET', 'POST'])
def chat():
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
is_authenticated = 'username' in session
chat_messages = data['general_chat']
if is_authenticated:
data['users'][username]['last_chat_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_data(data)
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
if request.method == 'POST' and is_authenticated:
message = request.form.get('message')
file = request.files.get('file')
post_id = request.form.get('post_id')
message_data = {
'sender': username,
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if message:
message_data['text'] = message
if file and file.filename:
filename = secure_filename(file.filename)
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(temp_path)
file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
api = HfApi()
file_path = f"chat_files/general/{filename}"
api.upload_file(
path_or_fileobj=temp_path,
path_in_repo=file_path,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Uploaded chat file by {username}"
)
message_data['file'] = file_path
message_data['file_type'] = file_type
if os.path.exists(temp_path):
os.remove(temp_path)
if post_id:
post = next((p for p in data['posts'] if p['id'] == post_id and p['uploader'] == username), None)
if post:
message_data['post_id'] = post_id
if 'text' in message_data or 'file' in message_data or 'post_id' in message_data:
data['general_chat'].append(message_data)
save_data(data)
return redirect(url_for('chat'))
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>General Chat - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 900px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2.2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.messages {
max-height: 500px;
overflow-y: auto;
padding: 15px;
background: var(--glass-bg);
border-radius: 14px;
margin-bottom: 20px;
scroll-behavior: smooth;
}
.message {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
gap: 12px;
max-width: 75%;
}
.message.sent {
margin-left: auto;
flex-direction: row-reverse;
}
.message.received {
margin-right: auto;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
loading: lazy;
}
.message-content {
padding: 12px 18px;
background: var(--card-bg);
border-radius: 12px;
font-size: 1em;
transition: var(--transition);
box-shadow: var(--shadow);
border: 1px solid rgba(0, 0, 0, 0.05);
word-wrap: break-word;
}
body.dark .message-content {
background: var(--card-bg-dark);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.message.sent .message-content {
border-color: var(--primary);
}
.message.received .message-content {
border-color: var(--secondary);
}
.message:hover .message-content {
transform: translateY(-3px);
}
.message .sender {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.message .sender:hover {
color: var(--accent);
}
.message .time {
font-size: 0.85em;
color: rgba(0, 0, 0, 0.5);
margin-top: 8px;
display: block;
}
body.dark .message .time {
color: rgba(255, 255, 255, 0.5);
}
.message img, .message video {
max-width: 300px;
max-height: 300px;
width: 100%;
border-radius: 10px;
margin-top: 10px;
display: block;
loading: lazy;
}
.message a {
color: var(--primary);
text-decoration: none;
}
.message a:hover {
color: var(--accent);
}
.message-form {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.post-select {
padding: 12px;
border-radius: 12px;
background: var(--glass-bg);
color: var(--text-light);
}
body.dark .post-select {
color: var(--text-dark);
}
@media (max-width: 900px) {
.message-avatar {
width: 35px;
height: 35px;
}
.message {
max-width: 80%;
}
}
@media (max-width: 480px) {
.container {
padding: 20px;
}
.messages {
max-height: 400px;
}
.message-avatar {
width: 30px;
height: 30px;
}
.message {
max-width: 85%;
}
.message-content {
padding: 10px 15px;
font-size: 0.9em;
}
.message img, .message video {
max-width: 200px;
max-height: 200px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>General Chat</h1>
<div class="messages" id="messages">
{% for message in chat_messages %}
<div class="message {{ 'sent' if message['sender'] == username else 'received' }}">
{% if message['sender'] == username %}
{% if avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Your Avatar" class="message-avatar" loading="lazy">
{% else %}
<div class="message-avatar" style="background: var(--primary);"></div>
{% endif %}
{% else %}
{% if data['users'][message['sender']].get('avatar') %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ data['users'][message['sender']]['avatar'] }}" alt="{{ message['sender'] }} Avatar" class="message-avatar" loading="lazy">
{% else %}
<div class="message-avatar" style="background: var(--secondary);"></div>
{% endif %}
{% endif %}
<div class="message-content">
<a href="{{ url_for('user_profile', username=message['sender']) }}" class="sender">{{ message['sender'] }}</a>
<span class="status-dot {{ 'online' if is_user_online(message['sender']) else 'offline' }}"></span>
{% if 'text' in message %}
<p>{{ message['text'] }}</p>
{% endif %}
{% if 'file' in message %}
{% if message['file_type'] == 'video' %}
<video controls preload="metadata" loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ message['file'] }}" type="video/mp4">
</video>
{% else %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ message['file'] }}" alt="Chat file" loading="lazy" onclick="openModal(this.src)">
{% endif %}
{% endif %}
{% if 'post_id' in message %}
{% with post = posts|selectattr('id', 'equalto', message['post_id'])|first %}
{% if post %}
<p>Shared: <a href="{{ url_for('post_page', post_id=message['post_id']) }}">{{ post['title'] }}</a></p>
{% endif %}
{% endwith %}
{% endif %}
<span class="time">{{ message['time'] }}</span>
</div>
</div>
{% endfor %}
</div>
{% if is_authenticated %}
<form method="POST" enctype="multipart/form-data" class="message-form">
<textarea name="message" placeholder="Type a message" rows="3"></textarea>
<input type="file" name="file" accept="video/*,image/*">
<select name="post_id" class="post-select">
<option value="">Share a post (optional)</option>
{% for post in user_posts %}
<option value="{{ post['id'] }}">{{ post['title'] }}</option>
{% endfor %}
</select>
<button type="submit" class="btn">Send</button>
</form>
{% else %}
<p style="font-size: 1.1em;"><a href="{{ url_for('login') }}">Login</a> to send messages.</p>
{% endif %}
</div>
<div class="modal" id="imageModal" onclick="closeModal(event)">
<img id="modalImage" src="">
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function openModal(src) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'flex';
modalImg.src = src;
}
function closeModal(event) {
if (event.target.tagName !== 'IMG') {
document.getElementById('imageModal').style.display = 'none';
}
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
const messagesDiv = document.getElementById('messages');
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
</script>
</body>
</html>
'''
user_posts = [p for p in data['posts'] if p['uploader'] == username] if is_authenticated else []
avatar = data['users'][username].get('avatar') if is_authenticated else None
return render_template_string(html,
chat_messages=chat_messages,
username=username,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
posts=data['posts'],
user_posts=user_posts,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online,
is_user_online=lambda u: is_user_online(data, u),
data=data,
avatar=avatar)
@app.route('/users', methods=['GET', 'POST'])
def users():
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username) if is_authenticated else 0
private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0
user_count = len(data['users'])
is_online = is_user_online(data, username) if is_authenticated else False
search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else ''
user_list = [(user, data['users'][user].get('avatar'), is_user_online(data, user))
for user in data['users'] if not search_query or search_query in user.lower()]
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Users - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 900px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2.2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.search-container {
max-width: 700px;
margin: 0 auto 25px;
}
.search-form {
display: flex;
gap: 12px;
}
.search-input {
flex-grow: 1;
background: var(--glass-bg);
border-radius: 12px;
box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.1);
}
.search-btn {
padding: 12px 25px;
box-shadow: none;
}
.user-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.user-item {
background: var(--card-bg);
padding: 15px;
border-radius: 16px;
box-shadow: var(--shadow);
transition: var(--transition);
display: flex;
align-items: center;
gap: 15px;
}
body.dark .user-item {
background: var(--card-bg-dark);
}
.user-item:hover {
transform: translateY(-5px);
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
loading: lazy;
}
.user-info {
flex-grow: 1;
}
.user-info a {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.user-info a:hover {
color: var(--accent);
}
.message-btn {
background: var(--secondary);
padding: 10px 20px;
font-size: 0.9em;
}
.message-btn:hover {
background: #00b8c5;
}
@media (max-width: 480px) {
.user-grid {
grid-template-columns: 1fr;
}
.user-avatar {
width: 50px;
height: 50px;
}
.user-info a {
font-size: 1em;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>Users ({{ user_count }})</h1>
<div class="search-container">
<form method="POST" class="search-form">
<input type="text" name="search" class="search-input" placeholder="Search users" value="{{ search_query }}">
<button type="submit" class="btn search-btn">🔍</button>
</form>
</div>
<div class="user-grid">
{% for user, avatar, online in user_list %}
{% if user != username %}
<div class="user-item">
{% if avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="{{ user }} Avatar" class="user-avatar" loading="lazy">
{% else %}
<div class="user-avatar" style="background: var(--primary);"></div>
{% endif %}
<div class="user-info">
<a href="{{ url_for('user_profile', username=user) }}">{{ user }}</a>
<span class="status-dot {{ 'online' if online else 'offline' }}"></span>
{% if is_authenticated %}
<br><a href="{{ url_for('private_chat', recipient=user) }}" class="btn message-btn">Send Message</a>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html,
user_list=user_list,
username=username,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online,
search_query=search_query)
@app.route('/messages', methods=['GET'])
def messages():
if 'username' not in session:
flash('Login to view messages!')
return redirect(url_for('login'))
data = load_data()
username = session['username']
update_last_seen(data, username)
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username)
private_unread_count = get_private_unread_count(data, username)
user_count = len(data['users'])
is_online = is_user_online(data, username)
dialogs = {}
for chat_key, messages in data['private_chats'].items():
user1, user2 = chat_key.split('_')
if username not in (user1, user2):
continue
other_user = user1 if user2 == username else user2
last_message = messages[-1] if messages else None
unread = sum(1 for msg in messages if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > datetime.strptime(data['users'][username]['last_private_visit'], '%Y-%m-%d %H:%M:%S') and msg['sender'] != username)
dialogs[other_user] = {
'last_message': last_message,
'unread': unread,
'avatar': data['users'][other_user].get('avatar'),
'online': is_user_online(data, other_user)
}
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Messages - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 900px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2.2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.dialog-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.dialog-item {
background: var(--card-bg);
padding: 15px;
border-radius: 16px;
box-shadow: var(--shadow);
transition: var(--transition);
display: flex;
align-items: center;
gap: 15px;
}
body.dark .dialog-item {
background: var(--card-bg-dark);
}
.dialog-item:hover {
transform: translateY(-5px);
}
.dialog-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
loading: lazy;
}
.dialog-info {
flex-grow: 1;
}
.dialog-info a {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.dialog-info a:hover {
color: var(--accent);
}
.dialog-unread {
background: var(--secondary);
color: white;
padding: 5px 10px;
border-radius: 12px;
font-size: 0.8em;
font-weight: 700;
}
@media (max-width: 480px) {
.dialog-avatar {
width: 40px;
height: 40px;
}
.dialog-info a {
font-size: 0.95em;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>Messages</h1>
<div class="dialog-list">
{% for other_user, info in dialogs.items() %}
<div class="dialog-item">
{% if info.avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ info.avatar }}" alt="{{ other_user }} Avatar" class="dialog-avatar" loading="lazy">
{% else %}
<div class="dialog-avatar" style="background: var(--primary);"></div>
{% endif %}
<div class="dialog-info">
<a href="{{ url_for('private_chat', recipient=other_user) }}">{{ other_user }}</a>
<span class="status-dot {{ 'online' if info.online else 'offline' }}"></span>
{% if info.last_message %}
<p style="font-size: 0.9em; color: rgba(0, 0, 0, 0.5);">{{ info.last_message['time'] }}:
{% if 'text' in info.last_message %}{{ info.last_message['text']|truncate(30) }}{% elif 'file' in info.last_message %}File{% elif 'post_id' in info.last_message %}Post{% endif %}</p>
{% endif %}
</div>
{% if info.unread > 0 %}
<span class="dialog-unread">{{ info.unread }}</span>
{% endif %}
</div>
{% endfor %}
{% if not dialogs %}
<p style="font-size: 1.1em;">No messages yet.</p>
{% endif %}
</div>
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
};
</script>
</body>
</html>
'''
return render_template_string(html,
dialogs=dialogs,
username=username,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online)
@app.route('/chat/<recipient>', methods=['GET', 'POST'])
def private_chat(recipient):
if 'username' not in session:
flash('Login to start a chat!')
return redirect(url_for('login'))
data = load_data()
username = session['username']
update_last_seen(data, username)
if recipient not in data['users']:
return "User not found", 404
is_authenticated = 'username' in session
unread_count = get_unread_count(data, username)
private_unread_count = get_private_unread_count(data, username)
user_count = len(data['users'])
is_online = is_user_online(data, username)
data['users'][username]['last_private_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_data(data)
chat_key = f"{min(username, recipient)}_{max(username, recipient)}"
if chat_key not in data['private_chats']:
data['private_chats'][chat_key] = []
private_messages = data['private_chats'][chat_key]
if request.method == 'POST':
message = request.form.get('message')
file = request.files.get('file')
post_id = request.form.get('post_id')
message_data = {
'sender': username,
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if message:
message_data['text'] = message
if file and file.filename:
filename = secure_filename(file.filename)
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(temp_path)
file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo'
api = HfApi()
file_path = f"chat_files/private/{chat_key}/{filename}"
api.upload_file(
path_or_fileobj=temp_path,
path_in_repo=file_path,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Uploaded private chat file by {username} to {recipient}"
)
message_data['file'] = file_path
message_data['file_type'] = file_type
if os.path.exists(temp_path):
os.remove(temp_path)
if post_id:
post = next((p for p in data['posts'] if p['id'] == post_id and p['uploader'] == username), None)
if post:
message_data['post_id'] = post_id
if 'text' in message_data or 'file' in message_data or 'post_id' in message_data:
data['private_chats'][chat_key].append(message_data)
save_data(data)
return redirect(url_for('private_chat', recipient=recipient))
recipient_avatar = data['users'][recipient].get('avatar')
recipient_online = is_user_online(data, recipient)
avatar = data['users'][username].get('avatar') if is_authenticated else None
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="interracial porn, bbc porn, qos, queen of spades, big black cock">
<title>Chat with {{ recipient }} - Content Hub</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
<style>
''' + BASE_STYLE + '''
.container {
max-width: 900px;
background: var(--card-bg);
padding: 30px;
border-radius: 20px;
box-shadow: var(--shadow);
}
body.dark .container {
background: var(--card-bg-dark);
}
h1 {
font-size: 2.2em;
font-weight: 800;
margin-bottom: 25px;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
display: flex;
align-items: center;
gap: 15px;
}
.recipient-avatar, .message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow);
loading: lazy;
}
.messages {
max-height: 500px;
overflow-y: auto;
padding: 15px;
background: var(--glass-bg);
border-radius: 14px;
margin-bottom: 20px;
scroll-behavior: smooth;
}
.message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 15px;
max-width: 75%;
}
.message.sent {
margin-left: auto;
flex-direction: row-reverse;
}
.message.received {
margin-right: auto;
}
.message-content {
padding: 12px 18px;
background: var(--card-bg);
border-radius: 12px;
font-size: 1em;
transition: var(--transition);
box-shadow: var(--shadow);
border: 1px solid rgba(0, 0, 0, 0.05);
word-wrap: break-word;
}
body.dark .message-content {
background: var(--card-bg-dark);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.message.sent .message-content {
border-color: var(--primary);
}
.message.received .message-content {
border-color: var(--secondary);
}
.message:hover .message-content {
transform: translateY(-3px);
}
.message .sender {
color: var(--primary);
font-weight: 600;
text-decoration: none;
}
.message .sender:hover {
color: var(--accent);
}
.message .time {
font-size: 0.85em;
color: rgba(0, 0, 0, 0.5);
margin-top: 8px;
display: block;
}
body.dark .message .time {
color: rgba(255, 255, 255, 0.5);
}
.message img, .message video {
max-width: 300px;
max-height: 300px;
width: 100%;
border-radius: 10px;
margin-top: 10px;
display: block;
loading: lazy;
}
.message a {
color: var(--primary);
text-decoration: none;
}
.message a:hover {
color: var(--accent);
}
.message-form {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
.post-select {
padding: 12px;
border-radius: 12px;
background: var(--glass-bg);
color: var(--text-light);
}
body.dark .post-select {
color: var(--text-dark);
}
@media (max-width: 900px) {
.message-avatar, .recipient-avatar {
width: 35px;
height: 35px;
}
.message {
max-width: 80%;
}
}
@media (max-width: 480px) {
.container {
padding: 20px;
}
.messages {
max-height: 400px;
}
.message-avatar, .recipient-avatar {
width: 30px;
height: 30px;
}
.message {
max-width: 85%;
}
.message-content {
padding: 10px 15px;
font-size: 0.9em;
}
.message img, .message video {
max-width: 200px;
max-height: 200px;
}
}
</style>
</head>
<body>
<button class="menu-btn" onclick="toggleSidebar()">☰</button>
''' + NAV_HTML + '''
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="container">
<h1>
{% if recipient_avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ recipient_avatar }}" alt="{{ recipient }} Avatar" class="recipient-avatar" loading="lazy">
{% else %}
<div class="recipient-avatar" style="background: var(--primary);"></div>
{% endif %}
Chat with {{ recipient }} <span class="status-dot {{ 'online' if recipient_online else 'offline' }}"></span>
</h1>
<div class="messages" id="messages">
{% for message in private_messages %}
<div class="message {{ 'sent' if message['sender'] == username else 'received' }}">
{% if message['sender'] == username %}
{% if avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ avatar }}" alt="Your Avatar" class="message-avatar" loading="lazy">
{% else %}
<div class="message-avatar" style="background: var(--primary);"></div>
{% endif %}
{% else %}
{% if recipient_avatar %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ recipient_avatar }}" alt="{{ recipient }} Avatar" class="message-avatar" loading="lazy">
{% endif %}
{% endif %}
<div class="message-content">
<a href="{{ url_for('user_profile', username=message['sender']) }}" class="sender">{{ message['sender'] }}</a>
<span class="status-dot {{ 'online' if is_user_online(message['sender']) else 'offline' }}"></span>
{% if 'text' in message %}
<p>{{ message['text'] }}</p>
{% endif %}
{% if 'file' in message %}
{% if message['file_type'] == 'video' %}
<video controls preload="metadata" loading="lazy">
<source src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ message['file'] }}" type="video/mp4">
</video>
{% else %}
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ message['file'] }}" alt="Chat file" loading="lazy" onclick="openModal(this.src)">
{% endif %}
{% endif %}
{% if 'post_id' in message %}
{% with post = posts|selectattr('id', 'equalto', message['post_id'])|first %}
{% if post %}
<p>Shared: <a href="{{ url_for('post_page', post_id=message['post_id']) }}">{{ post['title'] }}</a></p>
{% endif %}
{% endwith %}
{% endif %}
<span class="time">{{ message['time'] }}</span>
</div>
</div>
{% endfor %}
</div>
<form method="POST" enctype="multipart/form-data" class="message-form">
<textarea name="message" placeholder="Type a message" rows="3"></textarea>
<input type="file" name="file" accept="video/*,image/*">
<select name="post_id" class="post-select">
<option value="">Share a post (optional)</option>
{% for post in user_posts %}
<option value="{{ post['id'] }}">{{ post['title'] }}</option>
{% endfor %}
</select>
<button type="submit" class="btn">Send</button>
</form>
</div>
<div class="modal" id="imageModal" onclick="closeModal(event)">
<img id="modalImage" src="">
</div>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('active');
}
function toggleTheme() {
document.body.classList.toggle('dark');
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
}
function openModal(src) {
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
modal.style.display = 'flex';
modalImg.src = src;
}
function closeModal(event) {
if (event.target.tagName !== 'IMG') {
document.getElementById('imageModal').style.display = 'none';
}
}
window.onload = () => {
if (localStorage.getItem('theme') === 'dark') document.body.classList.add('dark');
const messagesDiv = document.getElementById('messages');
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
</script>
</body>
</html>
'''
user_posts = [p for p in data['posts'] if p['uploader'] == username]
return render_template_string(html,
private_messages=private_messages,
recipient=recipient,
username=username,
is_authenticated=is_authenticated,
repo_id=REPO_ID,
posts=data['posts'],
user_posts=user_posts,
unread_count=unread_count,
user_count=user_count,
private_unread_count=private_unread_count,
is_online=is_online,
is_user_online=lambda u: is_user_online(data, u),
recipient_avatar=recipient_avatar,
recipient_online=recipient_online,
avatar=avatar)
if __name__ == '__main__':
threading.Thread(target=periodic_backup, daemon=True).start()
app.run(debug=False, host='0.0.0.0', port=7860)