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"
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("Данные не в формате dict, инициализация пустой базы")
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("Данные успешно загружены")
return data
except Exception as e:
logging.error(f"Ошибка при загрузке данных: {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("Данные сохранены и загружены на HF")
except Exception as e:
logging.error(f"Ошибка при сохранении данных: {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"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
logging.info("База данных загружена на Hugging Face")
except Exception as e:
logging.error(f"Ошибка при загрузке базы данных: {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("База данных скачана с Hugging Face")
except Exception as e:
logging.error(f"Ошибка при скачивании базы данных: {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: #f7f9fc;
--background-dark: #1e1b2e;
--card-bg: rgba(255, 255, 255, 0.97);
--card-bg-dark: rgba(40, 35, 60, 0.97);
--text-light: #2a1e5a;
--text-dark: #e8e1ff;
--shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
--glass-bg: rgba(255, 255, 255, 0.2);
--transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
--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.7;
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(15px);
padding: 30px;
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: 40px;
}
.nav-brand {
font-size: 2em;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
color: transparent;
}
.logo {
width: 45px;
height: 45px;
border-radius: 16px;
box-shadow: var(--shadow);
transition: transform var(--transition);
}
.logo:hover {
transform: scale(1.1);
}
.nav-links {
display: flex;
flex-direction: column;
gap: 15px;
}
.nav-link {
display: flex;
align-items: center;
gap: 15px;
padding: 15px 25px;
background: var(--card-bg);
color: var(--text-light);
text-decoration: none;
border-radius: 16px;
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(8px);
background: var(--primary);
color: white;
box-shadow: 0 8px 25px rgba(255, 77, 109, 0.5);
}
.nav-link .badge {
position: absolute;
right: 15px;
background: var(--secondary);
color: white;
padding: 5px 12px;
border-radius: 14px;
font-size: 0.85em;
font-weight: 700;
}
.logout-btn {
background: var(--secondary);
color: white;
}
.logout-btn:hover {
background: #00b8c5;
}
.menu-btn {
display: none;
font-size: 30px;
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: 30px;
transition: var(--transition);
}
.btn {
padding: 15px 30px;
background: var(--primary);
color: white;
border: none;
border-radius: 16px;
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 10px 30px rgba(255, 77, 109, 0.6);
}
input, textarea, select {
width: 100%;
padding: 15px;
margin: 12px 0;
border: none;
border-radius: 16px;
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.25);
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.9);
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.4s ease;
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--glass-bg);
border: none;
padding: 12px;
border-radius: 50%;
cursor: pointer;
font-size: 26px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.theme-toggle:hover {
transform: rotate(90deg);
background: var(--accent);
color: white;
}
.status-dot {
width: 12px;
height: 12px;
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.85); }
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.6em; }
.nav-link { font-size: 1em; padding: 12px 20px; }
.btn { padding: 12px 20px; font-size: 1em; }
.logo { width: 40px; height: 40px; }
}
'''
NAV_HTML = '''
'''
@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('Пользователь уже существует!')
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('Регистрация успешна! Пожалуйста, войдите.')
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 = '''
Register - Content Hub
''' + NAV_HTML + '''
Register
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
Already have an account? Login
'''
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('Неверное имя пользователя или пароль!')
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 = '''
Login - Content Hub
''' + NAV_HTML + '''
Login
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
Don't have an account? Register
'''
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/', 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/', 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
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 "Пост не найден", 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')
}]
save_data(data)
return redirect(url_for('feed'))
html = '''
Adusis - QoS, BBC, BNWO HUB
''' + NAV_HTML + '''
{% for post in posts %}
{% if post['type'] == 'video' %}
{% else %}
![{{ post['title'] }}](https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }})
{% endif %}
{{ post['title'] }}
{{ post['description']|truncate(150) }}
By: {{ post['uploader'] }}
{{ post['likes']|length }}
{{ post['comments']|length }}
{{ post['views'] }}
{% endfor %}
'''
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))
@app.route('/post/', 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 "Пост не найден", 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 = '''
{{ post['title'] }} - Content Hub
''' + NAV_HTML + '''
{{ post['title'] }}
{% if post['type'] == 'video' %}
{% else %}
![{{ post['title'] }}](https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ post['type'] }}s/{{ post['filename'] }})
{% endif %}
{{ post['description'] }}
By: {{ post['uploader'] }} | {{ post['upload_date'] }}
Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}
{% if is_authenticated %}
{% endif %}
Comments
{% for comment in post.get('comments', []) %}
{% endfor %}
Back to Feed
{% if not is_authenticated %}
Login to like and comment.
{% endif %}
'''
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('Войдите, чтобы просмотреть свой профиль!')
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"Загружен аватар для {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 = '''
Profile - {{ username }} - Content Hub
''' + NAV_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/')
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 "Пользователь не найден", 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')
is_user_online_status = is_user_online(data, username)
html = '''
{{ username }}'s Profile - Content Hub
''' + NAV_HTML + '''
'''
return render_template_string(html,
username=username,
current_user=current_user,
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,
is_user_online_status=is_user_online_status)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if 'username' not in session:
flash('Войдите, чтобы загрузить контент!')
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 file and title:
filename = secure_filename(file.filename)
file_type = 'video' if filename.endswith(('.mp4', '.mov', '.avi')) else 'image'
temp_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(temp_path)
api = HfApi()
file_path_in_repo = f"{file_type}s/{username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{filename}"
api.upload_file(
path_or_fileobj=temp_path,
path_in_repo=file_path_in_repo,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Загружен {file_type} от {username}"
)
post_id = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10))
data['posts'].append({
'id': post_id,
'title': title,
'description': description,
'uploader': username,
'filename': file_path_in_repo,
'type': file_type,
'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)
flash('Контент успешно загружен!')
return redirect(url_for('profile'))
html = '''
Upload Content - Content Hub
''' + NAV_HTML + '''
Upload Content
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}
'''
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('/chat', methods=['GET', 'POST'])
def chat():
data = load_data()
username = session.get('username', None)
if username:
update_last_seen(data, username)
data['users'][username]['last_chat_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_data(data)
is_authenticated = 'username' in session
chat_messages = sorted(data['general_chat'], key=lambda x: datetime.strptime(x['time'], '%Y-%m-%d %H:%M:%S'))
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')
if message or file or post_id:
msg = {
'sender': username,
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if message:
msg['text'] = message
if post_id:
msg['post_id'] = post_id
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.endswith(('.mp4', '.mov', '.avi')) else 'image'
api = HfApi()
file_path = f"chat_files/{username}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{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"Загружен файл чата от {username}"
)
msg['file'] = file_path
msg['file_type'] = file_type
if os.path.exists(temp_path):
os.remove(temp_path)
data['general_chat'].append(msg)
save_data(data)
return redirect(url_for('chat'))
html = '''
General Chat - Content Hub
''' + NAV_HTML + '''
General Chat
{% for msg in chat_messages %}
{% if 'file' in msg %}
{% if msg['file_type'] == 'video' %}
{% else %}

{% endif %}
{% endif %}
{{ msg['sender'] }} ({{ msg['time'] }})
{% if 'text' in msg %}
{{ msg['text'] }}
{% endif %}
{% if 'post_id' in msg %}
Shared post: View Post
{% endif %}
{% endfor %}
{% if is_authenticated %}
{% else %}
Login to send messages.
{% endif %}
'''
return render_template_string(html,
chat_messages=chat_messages,
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))
@app.route('/users')
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
users_list = sorted(data['users'].keys())
html = '''
Users - Content Hub
''' + NAV_HTML + '''
Users ({{ user_count }})
{% for user in users_list %}
{{ user }}
Last seen: {{ data['users'][user]['last_seen'] }}
{% if is_authenticated and user != username %}
Message
{% endif %}
{% endfor %}
'''
return render_template_string(html,
users_list=users_list,
data=data,
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('/messages', methods=['GET', 'POST'])
def messages():
if 'username' not in session:
flash('Войдите, чтобы просмотреть сообщения!')
return redirect(url_for('login'))
data = load_data()
username = session['username']
update_last_seen(data, username)
data['users'][username]['last_private_visit'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
save_data(data)
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)
private_chats = {k: v for k, v in data['private_chats'].items() if username in k.split('_')}
if request.method == 'POST':
recipient = request.form.get('recipient')
message = request.form.get('message')
file = request.files.get('file')
if recipient and (message or file):
if recipient not in data['users']:
flash('Получатель не найден!')
return redirect(url_for('messages'))
chat_key = '_'.join(sorted([username, recipient]))
if chat_key not in data['private_chats']:
data['private_chats'][chat_key] = []
msg = {
'sender': username,
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if message:
msg['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.endswith(('.mp4', '.mov', '.avi')) else 'image'
api = HfApi()
file_path = f"private_files/{chat_key}_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{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"Загружен файл приватного чата между {username} и {recipient}"
)
msg['file'] = file_path
msg['file_type'] = file_type
if os.path.exists(temp_path):
os.remove(temp_path)
data['private_chats'][chat_key].append(msg)
save_data(data)
return redirect(url_for('messages') + f'#chat_{recipient}')
html = '''
Private Messages - Content Hub
''' + NAV_HTML + '''
Private Messages
{% if private_chats %}
{% set active_chat = request.args.get('chat') or (private_chats.keys()|list|first).split('_')|reject('eq', username)|first %}
{% set chat_key = '_'.join(sorted([username, active_chat])) %}
{% set messages = private_chats.get(chat_key, []) %}
{% for msg in messages %}
{% if 'file' in msg %}
{% if msg['file_type'] == 'video' %}
{% else %}

{% endif %}
{% endif %}
{{ msg['sender'] }} ({{ msg['time'] }})
{% if 'text' in msg %}
{{ msg['text'] }}
{% endif %}
{% endfor %}
{% else %}
No private chats yet. Start one!
{% endif %}
'''
return render_template_string(html,
private_chats=private_chats,
data=data,
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))
if __name__ == '__main__':
threading.Thread(target=periodic_backup, daemon=True).start()
app.run(debug=True, host='0.0.0.0', port=7860)