diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,30 +1,25 @@ from flask import Flask, render_template_string, request, redirect, url_for, session, flash -from flask_caching import Cache # Добавляем кэширование import json import os import logging import threading import time -from datetime import datetime, timedelta +from datetime import datetime 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' +app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене +DATA_FILE = 'data_ostenhost.json' REPO_ID = "Eluza133/w1f9" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен для записи +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) +logging.basicConfig(level=logging.DEBUG) # Функции работы с базой данных -@cache.memoize(timeout=300) # Кэшируем данные на 5 минут def load_data(): try: download_db_from_hf() @@ -32,30 +27,27 @@ def load_data(): 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') + return {'posts': [], 'users': {}, 'pending_organizations': []} + if 'posts' not in data: + data['posts'] = [] + if 'users' not in data: + data['users'] = {} + if 'pending_organizations' not in data: + data['pending_organizations'] = [] logging.info("Данные успешно загружены") return data except Exception as e: - logging.error(f"Ошибка при загрузке данных: {e}") - return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} + logging.error(f"Ошибка загрузки данных: {e}") + return {'posts': [], 'users': {}, 'pending_organizations': []} 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}") + logging.error(f"Ошибка сохранения данных: {e}") raise def upload_db_to_hf(): @@ -67,11 +59,11 @@ def upload_db_to_hf(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) logging.info("База данных загружена на Hugging Face") except Exception as e: - logging.error(f"Ошибка при загрузке базы данных: {e}") + logging.error(f"Ошибка загрузки базы: {e}") def download_db_from_hf(): try: @@ -85,806 +77,706 @@ def download_db_from_hf(): ) logging.info("База данных скачана с Hugging Face") except Exception as e: - logging.error(f"Ошибка при скачивании базы данных: {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) + json.dump({'posts': [], 'users': {}, 'pending_organizations': []}, f) def periodic_backup(): while True: upload_db_to_hf() - time.sleep(1800) # Увеличиваем интервал до 30 минут для снижения нагрузки - -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) + time.sleep(800) -# Обновленный стиль +# Обновленный базовый стиль BASE_STYLE = ''' -:root { - --primary: #ff4d6d; - --secondary: #00ddeb; - --accent: #8b5cf6; - --background-light: #fef9f1; - --background-dark: #1f1a44; - --card-bg: rgba(255, 255, 255, 0.9); - --card-bg-dark: rgba(50, 45, 80, 0.9); - --text-light: #2a1e5a; - --text-dark: #e8e1ff; - --shadow: 0 8px 24px rgba(0, 0, 0, 0.15); - --glass-bg: rgba(255, 255, 255, 0.1); - --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; - transition: var(--transition); - overflow-x: hidden; -} -body.dark { - background: var(--background-dark); - color: var(--text-dark); -} -.sidebar { - position: fixed; - top: 0; - left: 0; - width: 280px; - height: 100%; - background: var(--glass-bg); - backdrop-filter: blur(15px); - padding: 20px; - box-shadow: var(--shadow); - z-index: 1000; - transition: transform var(--transition); -} -.sidebar.hidden { - transform: translateX(-100%); -} -.sidebar-header { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 30px; -} -.nav-brand { - font-size: 1.6em; - font-weight: 800; - background: linear-gradient(135deg, var(--primary), var(--accent)); - -webkit-background-clip: text; - color: transparent; -} -.logo { - width: 36px; - height: 36px; - border-radius: 12px; - box-shadow: var(--shadow); -} -.nav-links { - display: flex; - flex-direction: column; - gap: 12px; -} -.nav-link { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 20px; - background: var(--card-bg); - color: var(--text-light); - text-decoration: none; - border-radius: 12px; - font-size: 1em; - font-weight: 500; - transition: var(--transition); - position: relative; -} -body.dark .nav-link { - background: var(--card-bg-dark); - color: var(--text-dark); -} -.nav-link:hover { - transform: scale(1.02); - background: var(--primary); - color: white; - box-shadow: 0 4px 16px rgba(255, 77, 109, 0.3); -} -.nav-link .badge { - position: absolute; - right: 12px; - background: var(--secondary); - color: white; - padding: 3px 8px; - border-radius: 10px; - font-size: 0.75em; - font-weight: 700; -} -.logout-btn { - background: var(--secondary); - color: white; -} -.logout-btn:hover { - background: #00b8c5; -} -.menu-btn { - display: none; - font-size: 24px; - background: var(--glass-bg); - border: none; - color: var(--primary); - cursor: pointer; - position: fixed; - top: 15px; - left: 15px; - z-index: 1001; - padding: 10px; - border-radius: 50%; - box-shadow: var(--shadow); - transition: var(--transition); -} -.menu-btn:hover { - background: var(--primary); - color: white; -} -.container { - margin: 20px auto 20px 300px; - max-width: 1200px; - padding: 20px; - transition: var(--transition); -} -.btn { - padding: 12px 24px; - background: var(--primary); - color: white; - border: none; - border-radius: 12px; - cursor: pointer; - font-size: 1em; - font-weight: 600; - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 8px; - box-shadow: var(--shadow); -} -.btn:hover { - transform: scale(1.03); - background: #e6415f; - box-shadow: 0 6px 20px rgba(255, 77, 109, 0.4); -} -input, textarea, select { - width: 100%; - padding: 12px; - margin: 10px 0; - border: none; - border-radius: 12px; - background: var(--glass-bg); - color: var(--text-light); - font-size: 1em; - transition: var(--transition); - box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.05); -} -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.15); - box-shadow: 0 0 0 3px var(--primary); -} -.modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - z-index: 2000; - justify-content: center; - align-items: center; - transition: opacity var(--transition); -} -.modal img, .modal video { - max-width: 90%; - max-height: 90%; - object-fit: contain; - border-radius: 16px; - box-shadow: var(--shadow); - animation: zoomIn 0.3s ease; -} -.theme-toggle { - position: fixed; - top: 15px; - right: 15px; - background: var(--glass-bg); - border: none; - padding: 10px; - border-radius: 50%; - cursor: pointer; - font-size: 20px; - box-shadow: var(--shadow); - transition: var(--transition); -} -.theme-toggle:hover { - transform: rotate(90deg); - background: var(--accent); - color: white; -} -.status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - display: inline-block; - margin-left: 5px; -} -.online { background: var(--online); } -.offline { background: var(--offline); } -@keyframes zoomIn { - from { opacity: 0; transform: scale(0.95); } - to { opacity: 1; transform: scale(1); } -} -@media (max-width: 900px) { + :root { + --primary: #6b48ff; + --secondary: #ff4785; + --background-light: #f5f7ff; + --background-dark: #1a1a2e; + --card-bg-light: rgba(255, 255, 255, 0.95); + --card-bg-dark: rgba(40, 40, 60, 0.95); + --text-light: #1a1a2e; + --text-dark: #e0e0e0; + --shadow: 0 10px 40px rgba(0, 0, 0, 0.15); + --glass-bg: rgba(255, 255, 255, 0.15); + --transition: all 0.4s ease; + } + * { 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; + transition: var(--transition); + overflow-x: hidden; + } + body.dark { + background: var(--background-dark); + color: var(--text-dark); + } .sidebar { - width: 100%; - max-width: 280px; - transform: translateX(-100%); + position: fixed; + top: 0; + left: 0; + width: 300px; + height: 100%; + background: var(--glass-bg); + backdrop-filter: blur(20px); + padding: 30px; + box-shadow: var(--shadow); + z-index: 1000; + transition: var(--transition); + } + .sidebar-header { + margin-bottom: 40px; + } + .nav-brand { + font-size: 2em; + font-weight: 700; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + .nav-links { + display: flex; + flex-direction: column; + gap: 20px; } - .sidebar.active { - transform: translateX(0); + .nav-link { + display: flex; + align-items: center; + gap: 15px; + padding: 18px; + background: var(--card-bg-light); + color: var(--text-light); + text-decoration: none; + border-radius: 15px; + font-size: 1.1em; + transition: var(--transition); + } + body.dark .nav-link { + background: var(--card-bg-dark); + color: var(--text-dark); + } + .nav-link:hover { + transform: translateX(15px); + background: var(--primary); + color: white; + } + .logout-btn { + background: var(--secondary); + color: white; + } + .logout-btn:hover { + background: #e63970; } .menu-btn { - display: block; + display: none; + font-size: 32px; + 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); } .container { - margin: 70px 15px 20px 15px; - max-width: calc(100% - 30px); + margin: 30px auto 30px 330px; + max-width: 1200px; + padding: 30px; + transition: var(--transition); + } + .btn { + padding: 14px 28px; + background: var(--primary); + color: white; + border: none; + border-radius: 15px; + cursor: pointer; + font-size: 1.1em; + transition: var(--transition); + display: inline-block; + margin: 10px 0; + } + .btn:hover { + transform: scale(1.08); + background: #5439cc; + } + input, textarea { + width: 100%; + padding: 14px; + margin: 15px 0; + border: 2px solid rgba(0, 0, 0, 0.1); + border-radius: 15px; + background: var(--glass-bg); + color: var(--text-light); + font-size: 1.1em; + transition: var(--transition); + } + body.dark input, body.dark textarea { + border: 2px solid rgba(255, 255, 255, 0.1); + color: var(--text-dark); + } + input:focus, textarea:focus { + outline: none; + border-color: var(--primary); + background: rgba(255, 255, 255, 0.2); + } + .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; + } + .modal img { + max-width: 95%; + max-height: 95%; + object-fit: contain; + border-radius: 15px; + box-shadow: var(--shadow); } .theme-toggle { - top: 70px; + 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); + } + @media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + width: 280px; + } + .sidebar.active { + transform: translateX(0); + } + .menu-btn { + display: block; + } + .container { + margin: 80px 15px 30px 15px; + max-width: calc(100% - 30px); + } + .theme-toggle { + top: 80px; + right: 15px; + } + .btn, input, textarea { + font-size: 1em; + padding: 12px; + } } -} -@media (max-width: 480px) { - .nav-brand { font-size: 1.4em; } - .nav-link { font-size: 0.9em; padding: 10px 16px; } - .btn { padding: 10px 18px; font-size: 0.9em; } -} ''' NAV_HTML = ''' - + ''' # Регистрация @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': + user_type = request.form.get('user_type') 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')) - + + if user_type == 'seller': + org_name = request.form.get('org_name') + org_phone = request.form.get('org_phone') + is_online = request.form.get('is_online') == 'on' + org_address = request.form.get('org_address') if not is_online else None + + if not org_name or not org_phone: + flash('Укажите название организации и рабочий номер!') + return redirect(url_for('register')) + + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'seller', + 'verified': False + } + data['pending_organizations'].append({ + 'username': username, + 'org_name': org_name, + 'org_phone': org_phone, + 'org_address': org_address, + 'is_online': is_online, + 'submitted_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + save_data(data) + flash('Регистрация успешна! Ваша организация на проверке, мы с вами свяжемся.') + return redirect(url_for('login')) + else: + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'type': 'buyer', + 'verified': True + } + 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 %} -
- - - -
- -
- - - -''' - 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) + + + + + + Регистрация + + + + + + ''' + NAV_HTML + ''' + +
+

Регистрация

+ {% 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) -# Вход +# Авторизация @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') + data = load_data() 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('Неверное имя пользователя или пароль!') + 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 %} -
- - - -
- -
- - - -''' - 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) + + + + + + Вход + + + + + + ''' + NAV_HTML + ''' + +
+

Вход

+ {% 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) # Выход @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('/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 '', 204 - return 'Пост не найден', 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 + username = session.get('username', None) search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else request.args.get('search', '').strip().lower() if search_query: posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()] html = ''' - - - - - - - - Adusis - QoS, BBC, BNWO HUB - - - - - - ''' + NAV_HTML + ''' - -
-

Content Feed

-
-
- - -
+ .post-item p { + margin-bottom: 15px; + font-size: 1.1em; + } + .stats { + font-size: 1em; + color: rgba(0, 0, 0, 0.6); + } + body.dark .stats { + color: rgba(255, 255, 255, 0.6); + } + .username-link { + color: var(--primary); + font-weight: 600; + text-decoration: none; + } + .username-link:hover { + text-decoration: underline; + } + + + + + ''' + NAV_HTML + ''' + +
+

Лента публикаций

+
+
+ + +
+
+
+ {% for post in posts %} + + {% if post['type'] == 'video' %} + + {% else %} + {{ post['title'] }} + {% endif %} +

{{ post['title'] }}

+

{{ post['description'] }}

+

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

+

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

+ + {% endfor %} +
-
- {% for post in posts %} - - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-

{{ post['description']|truncate(100) }}

-

By: {{ post['uploader'] }} | {{ post['upload_date'] }}

-

Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}

- - {% endfor %} + -
- - - - -''' - return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, 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)) + } + 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; + }); + } + }); + }; + + + + ''' + return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query) -# Страница поста +# Страница публикации @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 + 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 + username = session.get('username', None) post['views'] = post.get('views', 0) + 1 save_data(data) @@ -902,166 +794,152 @@ def post_page(post_id): save_data(data) html = ''' - - - - - - - {{ post['title'] }} - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

{{ post['title'] }}

- {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% 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', []) %} -
-

{{ comment['user'] }} ({{ comment['date'] }}): {{ comment['text'] }}

-
- {% 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)) + p { + font-size: 1.2em; + margin-bottom: 20px; + } + .like-btn.liked { + background: var(--secondary); + } + .like-btn.liked:hover { + background: #e63970; + } + .comment-section { + margin-top: 40px; + } + .comment { + background: var(--glass-bg); + padding: 20px; + border-radius: 15px; + margin-bottom: 20px; + font-size: 1.1em; + } + .username-link { + color: var(--primary); + font-weight: 600; + text-decoration: none; + } + .username-link:hover { + text-decoration: underline; + } + + + + + ''' + NAV_HTML + ''' + +
+

{{ post['title'] }}

+ {% if post['type'] == 'video' %} + + {% else %} + {{ post['title'] }} + {% endif %} +

{{ post['description'] }}

+

Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}

+

Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}

+ {% if is_authenticated %} +
+ +
+
+ + +
+ {% endif %} +

Комментарии

+ {% for comment in post.get('comments', []) %} +
+

{{ comment['user'] }} ({{ comment['date'] }}): {{ comment['text'] }}

+
+ {% endfor %} + Назад к ленте + {% if not is_authenticated %} +

Войдите, чтобы ставить лайки и комментировать.

+ {% endif %} +
+ + + + + ''' + return render_template_string(html, post=post, repo_id=REPO_ID, is_authenticated=is_authenticated, username=username) # Профиль пользователя @app.route('/profile', methods=['GET', 'POST']) def profile(): if 'username' not in session: - flash('Войдите, чтобы просмотреть свой профиль!') + 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) @@ -1069,7 +947,8 @@ def profile(): 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_type = user_data.get('type', 'buyer') + verified = user_data.get('verified', False) if request.method == 'POST': if 'delete_post' in request.form: @@ -1095,7 +974,7 @@ def profile(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Загружен аватар для {username}" + commit_message=f"Добавлен аватар для {username}" ) data['users'][username]['avatar'] = avatar_path if os.path.exists(temp_path): @@ -1106,437 +985,402 @@ def profile(): return redirect(url_for('profile')) html = ''' - - - - - - - Profile - {{ username }} - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-
- {% if avatar %} - Avatar - {% endif %} -
-

{{ username }}

- {% if not is_online %} -

Last seen: {{ last_seen }}

+ .post-item { + background: var(--card-bg-light); + backdrop-filter: blur(20px); + padding: 25px; + border-radius: 20px; + box-shadow: var(--shadow); + transition: var(--transition); + } + body.dark .post-item { + background: var(--card-bg-dark); + } + .post-item:hover { + transform: translateY(-15px); + } + .post-preview { + width: 100%; + height: 240px; + object-fit: cover; + border-radius: 15px; + margin-bottom: 20px; + } + .post-item h3 { + font-size: 1.5em; + margin-bottom: 15px; + } + .delete-btn { + background: var(--secondary); + } + .delete-btn:hover { + background: #e63970; + } + .share-btn { + background: #10b981; + } + .share-btn:hover { + background: #0d9f6e; + } + h2 { + font-size: 2em; + margin: 40px 0 20px; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + + + + + ''' + NAV_HTML + ''' + +
+
+ {% if avatar %} + Аватар {% endif %} -

{{ bio }}

- {% if link %} -

{{ link }}

+
+

{{ username }}

+

{{ bio }}

+ {% if link %} +

{{ link }}

+ {% endif %} +

Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}

+

Просмотров: {{ total_views }} | Лайков: {{ total_likes }}

+ +
+
+

Редактировать профиль

+
+ + + + +
+ {% if user_type == 'seller' and verified %} + Добавить публикацию + {% endif %} +

Ваши публикации

+
+ {% if user_posts %} + {% for post in user_posts %} +
+ + {% if post['type'] == 'video' %} + + {% else %} + {{ post['title'] }} + {% endif %} +

{{ post['title'] }}

+
+

{{ post['description'] }}

+

{{ post['upload_date'] }}

+
+ + +
+
+ {% endfor %} + {% else %} +

Вы пока не загрузили ни одной публикации.

{% endif %} -

Views: {{ total_views }} | Likes: {{ total_likes }}

-
-

Edit Profile

-
- - - - -
- Add Post -

Your Posts

-
- {% if user_posts %} - {% for post in user_posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-
-

{{ post['description']|truncate(100) }}

-

{{ post['upload_date'] }}

-
- - -
-
- {% endfor %} - {% else %} -

You haven't uploaded any posts yet.

- {% endif %} + -
- - - - -''' - return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online, last_seen=last_seen) + } + function copyProfileLink() { + navigator.clipboard.writeText(window.location.href).then(() => { + alert('Ссылка скопирована!'); + }); + } + 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; + }); + } + }); + }; + + + + ''' + return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, user_type=user_type, verified=verified) # Профиль другого пользователя @app.route('/profile/') def user_profile(username): data = load_data() - session_username = session.get('username', None) - if session_username: - update_last_seen(data, session_username) 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, session_username) if is_authenticated else 0 - private_unread_count = get_private_unread_count(data, session_username) if is_authenticated else 0 - user_count = len(data['users']) - is_online = is_user_online(data, session_username) 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) - profile_is_online = is_user_online(data, username) - last_seen = user_data.get('last_seen', 'Never') + user_type = user_data.get('type', 'buyer') + verified = user_data.get('verified', False) html = ''' - - - - - - - Profile - {{ username }} - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-
- {% if avatar %} - Avatar - {% endif %} -
-

{{ username }}

- {% if not profile_is_online %} -

Last seen: {{ last_seen }}

+ .post-item { + background: var(--card-bg-light); + backdrop-filter: blur(20px); + padding: 25px; + border-radius: 20px; + box-shadow: var(--shadow); + transition: var(--transition); + } + body.dark .post-item { + background: var(--card-bg-dark); + } + .post-item:hover { + transform: translateY(-15px); + } + .post-preview { + width: 100%; + height: 240px; + object-fit: cover; + border-radius: 15px; + margin-bottom: 20px; + } + .post-item h3 { + font-size: 1.5em; + margin-bottom: 15px; + } + .share-btn { + background: #10b981; + } + .share-btn:hover { + background: #0d9f6e; + } + h2 { + font-size: 2em; + margin: 40px 0 20px; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + + + + + ''' + NAV_HTML + ''' + +
+
+ {% if avatar %} + Аватар {% endif %} -

{{ bio }}

- {% if link %} -

{{ link }}

+
+

{{ username }}

+

{{ bio }}

+ {% if link %} +

{{ link }}

+ {% endif %} +

Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}

+

Просмотров: {{ total_views }} | Лайков: {{ total_likes }}

+ +
+
+

Публикации

+
+ {% if user_posts %} + {% for post in user_posts %} +
+ + {% if post['type'] == 'video' %} + + {% else %} + {{ post['title'] }} + {% endif %} +

{{ post['title'] }}

+
+

{{ post['description'] }}

+

{{ post['upload_date'] }}

+
+ {% endfor %} + {% else %} +

Этот пользователь пока не загрузил ни одной публикации.

{% endif %} -

Views: {{ total_views }} | Likes: {{ total_likes }}

-

Posts

-
- {% if user_posts %} - {% for post in user_posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-
-

{{ post['description']|truncate(100) }}

-

{{ post['upload_date'] }}

-
- {% endfor %} - {% else %} -

No posts yet.

- {% endif %} + -
- - - - -''' - return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online, profile_is_online=profile_is_online, last_seen=last_seen) + } + function copyProfileLink() { + navigator.clipboard.writeText(window.location.href).then(() => { + alert('Ссылка скопирована!'); + }); + } + 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; + }); + } + }); + }; + + + + ''' + return render_template_string(html, username=username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, repo_id=REPO_ID, is_authenticated=is_authenticated, user_type=user_type, verified=verified) # Загрузка контента @app.route('/upload', methods=['GET', 'POST']) @@ -1547,26 +1391,25 @@ def upload(): 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) + user_data = data['users'].get(username, {}) + if user_data.get('type') != 'seller' or not user_data.get('verified'): + flash('Только проверенные продавцы могут загружать контент!') + return redirect(url_for('profile')) 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('Заголовок и файл обязательны!') - return redirect(url_for('upload')) + uploader = session['username'] + + if not title or not file: + return "Укажите название и выберите файл", 400 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() api.upload_file( @@ -1575,7 +1418,7 @@ def upload(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Uploaded post: {title} by {username}" + commit_message=f"Добавлена публикация: {title} пользователем {uploader}" ) data = load_data() @@ -1588,7 +1431,7 @@ def upload(): 'description': description, 'filename': filename, 'type': file_type, - 'uploader': username, + 'uploader': uploader, 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'views': 0, 'likes': [], @@ -1600,1374 +1443,341 @@ def upload(): os.remove(temp_path) return redirect(url_for('profile')) + is_authenticated = 'username' in session + username = session.get('username', None) html = ''' - - - - - - - Upload Content - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Upload Content

- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - - -
-
-
+ body.dark .container { + background: var(--card-bg-dark); + } + h1 { + font-size: 2.2em; + font-weight: 700; + text-align: center; + margin-bottom: 30px; + background: linear-gradient(45deg, var(--primary), var(--secondary)); + -webkit-background-clip: text; + color: transparent; + } + #progress-container { + margin-top: 20px; + height: 10px; + background: rgba(0, 0, 0, 0.1); + border-radius: 5px; + overflow: hidden; + } + body.dark #progress-container { + background: rgba(255, 255, 255, 0.1); + } + #progress-bar { + width: 0%; + height: 100%; + background: var(--primary); + transition: width 0.4s ease; + } + + + + + ''' + NAV_HTML + ''' + +
+

Загрузить контент

+
+ + + + +
+
+
+
-
- - - -''' - return render_template_string(html, username=username, is_authenticated=is_authenticated, unread_count=unread_count, user_count=user_count, private_unread_count=private_unread_count, is_online=is_online) + + + + ''' + return render_template_string(html, username=username, is_authenticated=is_authenticated) -# Общий чат -@app.route('/chat', methods=['GET', 'POST']) -def chat(): +# Админ-панель +@app.route('/admhosto', methods=['GET', 'POST']) +def admin_panel(): 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) + pending_orgs = data.get('pending_organizations', []) 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 + username = session.get('username', None) - 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 request.method == 'POST': + if 'delete' 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] + save_data(data) + return redirect(url_for('admin_panel')) + elif 'approve_org' in request.form: + org_username = request.form.get('username') + if org_username: + data['users'][org_username]['verified'] = True + data['pending_organizations'] = [org for org in pending_orgs if org['username'] != org_username] + save_data(data) + return redirect(url_for('admin_panel')) + elif 'reject_org' in request.form: + org_username = request.form.get('username') + if org_username: + data['pending_organizations'] = [org for org in pending_orgs if org['username'] != org_username] + save_data(data) + return redirect(url_for('admin_panel')) - 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')) + search_query = request.args.get('search', '').strip().lower() + if search_query: + posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()] html = ''' - - - - - - - General Chat - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

General Chat

-
- {% for message in chat_messages %} -
- {% if message['sender'] == username %} - {% if avatar %} - Your Avatar - {% else %} -
- {% endif %} - {% else %} - {% if data['users'][message['sender']].get('avatar') %} - {{ message['sender'] }} Avatar - {% else %} -
- {% endif %} - {% endif %} -
- {{ message['sender'] }} - - {% if 'text' in message %} -

{{ message['text'] }}

- {% endif %} - {% if 'file' in message %} - {% if message['file_type'] == 'video' %} - - {% else %} - Chat file - {% endif %} - {% endif %} - {% if 'post_id' in message %} - {% with post = posts|selectattr('id', 'equalto', message['post_id'])|first %} - {% if post %} -

Shared: {{ post['title'] }}

- {% endif %} - {% endwith %} - {% endif %} - {{ message['time'] }} -
-
- {% endfor %} -
- {% if is_authenticated %} -
- - - - -
- {% else %} -

Login to send messages.

- {% endif %} -
- - - - -''' - 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 = ''' - - - - - - - Users - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Users ({{ user_count }})

-
-
- - -
-
-
- {% for user, avatar, online in user_list %} - {% if user != username %} -
- {% if avatar %} - {{ user }} Avatar - {% else %} -
- {% endif %} - -
- {% endif %} - {% endfor %} -
-
- - - -''' - 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('Войдите, чтобы просмотреть сообщения!') - 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 = ''' - - - - - - - Messages - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Messages

-
- {% for other_user, info in dialogs.items() %} -
- {% if info.avatar %} - {{ other_user }} Avatar - {% else %} -
- {% endif %} -
- {{ other_user }} - - {% if info.last_message %} -

{{ info.last_message['time'] }}: - {% if 'text' in info.last_message %}{{ info.last_message['text']|truncate(25) }}{% elif 'file' in info.last_message %}File{% elif 'post_id' in info.last_message %}Post{% endif %}

- {% endif %} -
- {% if info.unread > 0 %} - {{ info.unread }} - {% endif %} -
- {% endfor %} - {% if not dialogs %} -

No messages yet.

- {% endif %} -
-
- - - -''' - 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/', methods=['GET', 'POST']) -def private_chat(recipient): - if 'username' not in session: - flash('Войдите, чтобы начать чат!') - return redirect(url_for('login')) - - data = load_data() - username = session['username'] - update_last_seen(data, username) - if recipient not in data['users']: - return "Пользователь не найден", 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 = ''' - - - - - - - Chat with {{ recipient }} - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

- {% if recipient_avatar %} - {{ recipient }} Avatar - {% else %} -
- {% endif %} - Chat with {{ recipient }} -

-
- {% for message in private_messages %} -
- {% if message['sender'] == username %} - {% if avatar %} - Your Avatar - {% else %} -
- {% endif %} - {% else %} - {% if recipient_avatar %} - {{ recipient }} Avatar - {% else %} -
- {% endif %} - {% endif %} -
- {{ message['sender'] }} - - {% if 'text' in message %} -

{{ message['text'] }}

- {% endif %} - {% if 'file' in message %} - {% if message['file_type'] == 'video' %} -
-
- - - - -
- Back to Messages -
- - - - -''' - 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), - avatar=avatar, - recipient_avatar=recipient_avatar, - recipient_online=recipient_online) - -# Админ-панель -@app.route('/admhosto', methods=['GET', 'POST']) -def admin_panel(): - 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.form.get('search', '').strip().lower() if request.method == 'POST' and 'search' in request.form else request.args.get('search', '').strip().lower() - 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 'delete' 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] - save_data(data) - return redirect(url_for('admin_panel')) - - html = ''' - - - - - - - Admin Panel - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Admin Panel: All Posts

-
-
- - -
-
-
- {% if posts %} - {% for post in posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-
-

{{ post['description']|truncate(100) }}

-

By: {{ post['uploader'] }} | {{ post['upload_date'] }}

-
- - -
-
- {% endfor %} - {% else %} -

No posts found.

- {% endif %} -
-
- - - - -''' - return render_template_string(html, posts=posts, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query, 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)) + 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; + }); + } + }); + }; + + + + ''' + return render_template_string(html, posts=posts, pending_orgs=pending_orgs, is_authenticated=is_authenticated, username=username, repo_id=REPO_ID, search_query=search_query) if __name__ == '__main__': backup_thread = threading.Thread(target=periodic_backup, daemon=True)