diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,4 +1,5 @@ from flask import Flask, render_template_string, request, redirect, url_for, session, flash +from flask_caching import Cache # Добавляем кэширование import json import os import logging @@ -8,19 +9,22 @@ from datetime import datetime, timedelta from huggingface_hub import HfApi, hf_hub_download from werkzeug.utils import secure_filename import random -import base64 app = Flask(__name__) -app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене +app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey") # Безопасный ключ из переменной окружения DATA_FILE = 'data_adusis.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 -# Логирование -logging.basicConfig(level=logging.DEBUG) +# Настройка кэширования +cache = Cache(app, config={'CACHE_TYPE': 'simple'}) -# Функции для работы с данными +# Настройка логирования +logging.basicConfig(level=logging.INFO) + +# Функции работы с базой данных +@cache.memoize(timeout=300) # Кэшируем данные на 5 минут def load_data(): try: download_db_from_hf() @@ -29,13 +33,14 @@ def load_data(): if not isinstance(data, dict): logging.warning("Данные не в формате dict, инициализация пустой базы") return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} - for key in ['posts', 'users', 'general_chat', 'private_chats']: - if key not in data: - data[key] = [] if key in ['posts', 'general_chat'] else {} + data.setdefault('posts', []) + data.setdefault('users', {}) + data.setdefault('general_chat', []) + data.setdefault('private_chats', {}) for user in data['users']: - for field in ['last_chat_visit', 'last_private_visit', 'last_seen']: - if field not in data['users'][user]: - data['users'][user][field] = '1970-01-01 00:00:00' + 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: @@ -47,6 +52,7 @@ def save_data(data): 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}") @@ -87,7 +93,7 @@ def download_db_from_hf(): def periodic_backup(): while True: upload_db_to_hf() - time.sleep(800) + time.sleep(1800) # Увеличиваем интервал до 30 минут для снижения нагрузки def get_unread_count(data, username): if username not in data['users']: @@ -117,20 +123,20 @@ def update_last_seen(data, username): data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') save_data(data) -# Критический CSS -CRITICAL_CSS = ''' +# Обновленный стиль +BASE_STYLE = ''' :root { --primary: #ff4d6d; --secondary: #00ddeb; --accent: #8b5cf6; --background-light: #fef9f1; --background-dark: #1f1a44; - --card-bg-light: rgba(255, 255, 255, 0.95); - --card-bg-dark: rgba(50, 45, 80, 0.95); + --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 10px 30px rgba(0, 0, 0, 0.2); - --glass-bg: rgba(255, 255, 255, 0.15); + --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; @@ -141,17 +147,12 @@ body { background: var(--background-light); color: var(--text-light); line-height: 1.6; -} -body.dark { background: var(--background-dark); color: var(--text-dark); } -''' - -# Полный CSS -FULL_CSS = CRITICAL_CSS + ''' -.container { - max-width: 1200px; - margin: 20px auto; - padding: 0 15px; transition: var(--transition); + overflow-x: hidden; +} +body.dark { + background: var(--background-dark); + color: var(--text-dark); } .sidebar { position: fixed; @@ -160,23 +161,23 @@ FULL_CSS = CRITICAL_CSS + ''' width: 280px; height: 100%; background: var(--glass-bg); - backdrop-filter: blur(20px); + backdrop-filter: blur(15px); padding: 20px; box-shadow: var(--shadow); z-index: 1000; - transform: translateX(0); - transition: var(--transition); - overflow-y: auto; + transition: transform var(--transition); +} +.sidebar.hidden { + transform: translateX(-100%); } -.sidebar.hidden { transform: translateX(-100%); } .sidebar-header { display: flex; align-items: center; - gap: 10px; + gap: 12px; margin-bottom: 30px; } .nav-brand { - font-size: 1.5em; + font-size: 1.6em; font-weight: 800; background: linear-gradient(135deg, var(--primary), var(--accent)); -webkit-background-clip: text; @@ -196,14 +197,14 @@ FULL_CSS = CRITICAL_CSS + ''' .nav-link { display: flex; align-items: center; - gap: 10px; + gap: 12px; padding: 12px 20px; - background: var(--card-bg-light); + background: var(--card-bg); color: var(--text-light); text-decoration: none; border-radius: 12px; font-size: 1em; - font-weight: 600; + font-weight: 500; transition: var(--transition); position: relative; } @@ -212,19 +213,53 @@ body.dark .nav-link { color: var(--text-dark); } .nav-link:hover { - transform: translateX(5px); + transform: scale(1.02); background: var(--primary); color: white; - box-shadow: 0 4px 15px rgba(255, 77, 109, 0.4); + box-shadow: 0 4px 16px rgba(255, 77, 109, 0.3); } .nav-link .badge { position: absolute; - right: 10px; + right: 12px; background: var(--secondary); color: white; - padding: 3px 6px; + 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; @@ -236,11 +271,15 @@ body.dark .nav-link { 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%; @@ -252,10 +291,14 @@ input, textarea, select { 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); } -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 { @@ -265,16 +308,19 @@ input:focus, textarea:focus, select:focus { left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.9); + 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: 90vh; - border-radius: 15px; + max-height: 90%; + object-fit: contain; + border-radius: 16px; box-shadow: var(--shadow); + animation: zoomIn 0.3s ease; } .theme-toggle { position: fixed; @@ -294,22 +340,6 @@ input:focus, textarea:focus, select:focus { background: var(--accent); color: white; } -.menu-btn { - display: none; - position: fixed; - top: 15px; - left: 15px; - background: var(--glass-bg); - border: none; - padding: 10px; - border-radius: 50%; - cursor: pointer; - font-size: 24px; - box-shadow: var(--shadow); - z-index: 1001; - transition: var(--transition); -} -.menu-btn:hover { background: var(--primary); color: white; } .status-dot { width: 8px; height: 8px; @@ -319,25 +349,41 @@ input:focus, textarea:focus, select:focus { } .online { background: var(--online); } .offline { background: var(--offline); } -@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes slideIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } -@media (max-width: 768px) { +@keyframes zoomIn { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } +} +@media (max-width: 900px) { .sidebar { width: 100%; max-width: 280px; transform: translateX(-100%); } - .sidebar.active { transform: translateX(0); } - .menu-btn { display: block; } - .container { margin: 60px 10px 20px; } - .theme-toggle { top: 60px; right: 10px; } + .sidebar.active { + transform: translateX(0); + } + .menu-btn { + display: block; + } + .container { + margin: 70px 15px 20px 15px; + max-width: calc(100% - 30px); + } + .theme-toggle { + top: 70px; + } +} +@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 = ''' ''' -BASE_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 = ''' @@ -367,125 +446,91 @@ BASE_HTML = ''' - {% block title %}{% endblock %} - - - + Register - Content Hub + + - {{ NAV_HTML | safe }} + ''' + NAV_HTML + '''
- {% block content %}{% endblock %} -
- - {% block scripts %}{% endblock %} ''' - -# Регистрация -@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 = BASE_HTML.replace('{% block title %}{% endblock %}', 'Register - Content Hub') + ''' -{% block content %} - -
-

Register

- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - -
- -
-{% endblock %} -''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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']) @@ -494,6 +539,7 @@ def login(): 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 @@ -501,7 +547,7 @@ def login(): return redirect(url_for('feed')) flash('Неверное имя пользователя или пароль!') return redirect(url_for('login')) - + is_authenticated = 'username' in session username = session.get('username', None) if is_authenticated: @@ -510,56 +556,99 @@ def login(): 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 = BASE_HTML.replace('{% block title %}{% endblock %}', 'Login - Content Hub') + ''' -{% block content %} - -
-

Login

- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - -
- -
-{% endblock %} + + 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, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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') @@ -571,7 +660,7 @@ def logout(): session.pop('username', None) return redirect(url_for('feed')) -# Увеличение просмотров +# Увеличение счетчика просмотров @app.route('/increment_view/', methods=['POST']) def increment_view(post_id): data = load_data() @@ -600,227 +689,374 @@ def feed(): if search_query: posts = [post for post in posts if search_query in post['title'].lower() or search_query in post['description'].lower()] - html = BASE_HTML.replace('{% block title %}{% endblock %}', 'Adusis - QoS, BBC, BNWO HUB') + ''' -{% block content %} - -

Content Feed

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

{{ post['title'] }}

-

{{ post['description'] }}

-

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

-

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

- - {% endfor %} -
-{% block scripts %} - -{% endblock %} -{% endblock %} -''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_HTML, is_user_online=lambda u: is_user_online(data, u), 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) - -# Страница поста -@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 'like' in request.form: - if username not in post.get('likes', []): - post['likes'] = post.get('likes', []) + [username] - else: - post['likes'] = [user for user in post.get('likes', []) if user != username] - save_data(data) - elif '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 = BASE_HTML.replace('{% block title %}{% endblock %}', f"{post['title']} - Content Hub") + ''' -{% block content %} - -
-

{{ 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 %} -
-{% endblock %} -''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_HTML, is_user_online=lambda u: is_user_online(data, u), 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) - -# Профиль пользователя -@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) + html = ''' + + + + + + + + Adusis - QoS, BBC, BNWO HUB + + + + + + ''' + NAV_HTML + ''' + +
+

Content Feed

+
+
+ + +
+
+
+ {% 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)) + +# Страница поста +@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 'like' in request.form: + if username not in post.get('likes', []): + post['likes'] = post.get('likes', []) + [username] + else: + post['likes'] = [user for user in post.get('likes', []) if user != username] + save_data(data) + elif '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'] }} + {% 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)) + +# Профиль пользователя +@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) @@ -869,113 +1105,224 @@ def profile(): save_data(data) return redirect(url_for('profile')) - html = BASE_HTML.replace('{% block title %}{% endblock %}', f"Profile - {username} - Content Hub") + ''' -{% block content %} - -
- {% if avatar %} - Avatar - {% endif %} -
-

{{ username }}

- {% if not is_online %} -

Last seen: {{ last_seen }}

- {% endif %} -

{{ bio }}

- {% if link %} -

{{ link }}

- {% 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'] }} + html = ''' + + + + + + + Profile - {{ username }} - Content Hub + + + + + + ''' + NAV_HTML + ''' + +
+
+ {% if avatar %} + Avatar + {% endif %} +
+

{{ username }}

+ {% if not is_online %} +

Last seen: {{ last_seen }}

{% endif %} -

{{ post['title'] }}

-
-

{{ post['description'] }}

-

{{ post['upload_date'] }}

-
- - -
+

{{ bio }}

+ {% if link %} +

{{ link }}

+ {% endif %} +

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

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

No posts yet.

- {% endif %} -
-{% block scripts %} - -{% endblock %} -{% endblock %} +

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, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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) # Профиль другого пользователя @app.route('/profile/') @@ -1002,104 +1349,200 @@ def user_profile(username): profile_is_online = is_user_online(data, username) last_seen = user_data.get('last_seen', 'Never') - html = BASE_HTML.replace('{% block title %}{% endblock %}', f"Profile - {username} - Content Hub") + ''' -{% block content %} - -
- {% if avatar %} - Avatar - {% endif %} -
-

{{ username }}

- {% if not profile_is_online %} -

Last seen: {{ last_seen }}

- {% endif %} -

{{ bio }}

- {% if link %} -

{{ link }}

- {% endif %} -

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

- {% if is_authenticated %} - Message - {% endif %} -
-
-

{{ username }}'s Posts

-
- {% if user_posts %} - {% for post in user_posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} + html = ''' + + + + + + + Profile - {{ username }} - Content Hub + + + + + + ''' + NAV_HTML + ''' + +
+
+ {% if avatar %} + Avatar + {% endif %} +
+

{{ username }}

+ {% if not profile_is_online %} +

Last seen: {{ last_seen }}

{% endif %} -

{{ post['title'] }}

-
-

{{ post['description'] }}

-

{{ post['upload_date'] }}

+

{{ bio }}

+ {% if link %} +

{{ link }}

+ {% endif %} +

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

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

No posts yet.

- {% endif %} -
-{% block scripts %} - -{% endblock %} -{% endblock %} +

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, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_HTML, user_posts=user_posts, total_views=total_views, total_likes=total_likes, - bio=bio, link=link, avatar=avatar, username=username, profile_is_online=profile_is_online, - last_seen=last_seen, 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) + 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) # Загрузка контента @app.route('/upload', methods=['GET', 'POST']) def upload(): if 'username' not in session: + flash('Войдите, чтобы загрузить контент!') return redirect(url_for('login')) data = load_data() @@ -1113,12 +1556,12 @@ def upload(): if request.method == 'POST': title = request.form.get('title') - description = request.form.get('description', '') + description = request.form.get('description') file = request.files.get('file') if not file or not title: - flash('Title and file are required!') + flash('Заголовок и файл обязательны!') return redirect(url_for('upload')) - + filename = secure_filename(file.filename) temp_path = os.path.join('uploads', filename) os.makedirs('uploads', exist_ok=True) @@ -1152,65 +1595,124 @@ def upload(): 'comments': [] }) save_data(data) + if os.path.exists(temp_path): os.remove(temp_path) return redirect(url_for('profile')) - html = BASE_HTML.replace('{% block title %}{% endblock %}', 'Upload Content - Content Hub') + ''' -{% block content %} - -
-

Upload Content

-
- - - - -
-
-
-{% block scripts %} - -{% endblock %} -{% endblock %} + + + ''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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) # Общий чат @app.route('/chat', methods=['GET', 'POST']) @@ -1236,7 +1738,10 @@ def chat(): 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')} + message_data = { + 'sender': username, + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } if message: message_data['text'] = message @@ -1246,6 +1751,7 @@ def chat(): 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}" @@ -1270,180 +1776,169 @@ def chat(): 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 = BASE_HTML.replace('{% block title %}{% endblock %}', 'General Chat - Content Hub') + ''' -{% block content %} - -
-

General Chat

-
- {% for message in chat_messages %} -
- {{ message['sender'] }} - - ({{ message['time'] }}): - {% 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 %} -
- {% endfor %} -
- {% if is_authenticated %} -
- - - - -
- {% else %} -

Login to send messages.

- {% endif %} -
-{% block scripts %} - -{% endblock %} -{% endblock %} -''' - user_posts = [p for p in data['posts'] if p['uploader'] == username] if is_authenticated else [] - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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)) - -# Страница пользователей -@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 - user_list = [(u, data['users'][u].get('avatar'), is_user_online(data, u)) for u in data['users']] - - search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else '' - if search_query: - user_list = [u for u in user_list if search_query in u[0].lower()] - - html = BASE_HTML.replace('{% block title %}{% endblock %}', 'Users - Content Hub') + ''' -{% block content %} - -
-

Users ({{ user_count }})

-
-
- - -
-
-
- {% for user, avatar, online in user_list %} - {% if user != username %} -
- {% if avatar %} - {{ user }} Avatar - {% else %} -
- {% endif %} - -
-{% endblock %} + + + ''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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: @@ -1474,66 +1969,142 @@ def messages(): 'online': is_user_online(data, other_user) } - html = BASE_HTML.replace('{% block title %}{% endblock %}', 'Messages - Content Hub') + ''' -{% block content %} - -
-

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 %}

+ 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 %}
- {% if info.unread > 0 %} - {{ info.unread }} - {% endif %} -
- {% endfor %} - {% if not dialogs %} -

No messages yet.

- {% endif %} + {% endfor %} + {% if not dialogs %} +

No messages yet.

+ {% endif %} +
-
-{% endblock %} + + + ''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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']) @@ -1567,7 +2138,10 @@ def private_chat(recipient): 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')} + message_data = { + 'sender': username, + 'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } if message: message_data['text'] = message @@ -1577,6 +2151,7 @@ def private_chat(recipient): 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}" @@ -1601,125 +2176,298 @@ def private_chat(recipient): 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 = BASE_HTML.replace('{% block title %}{% endblock %}', f"Chat with {recipient} - Content Hub") + ''' -{% block content %} - -
-

- {% 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 + 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 %} -
- {% endif %} - {% endif %} -
- {{ message['sender'] }} - - {% if 'text' in message %}

{{ message['text'] }}

{% endif %} - {% if 'file' in message %} - {% if message['file_type'] == 'video' %} - + {% if recipient_avatar %} + {{ recipient }} Avatar {% 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'] }}

+
+ {{ message['sender'] }} + + {% if 'text' in message %} +

{{ message['text'] }}

+ {% endif %} + {% if 'file' in message %} + {% if message['file_type'] == 'video' %} + + {% else %} + Chat file {% endif %} - {% endwith %} - {% endif %} - {{ message['time'] }} + {% 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 %} -
-
- - - - -
- Back to Messages -
-{% block scripts %} - -{% endblock %} -{% endblock %} +
+
+ + + + +
+ Back to Messages +
+ + + + ''' user_posts = [p for p in data['posts'] if p['uploader'] == username] - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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) + 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']) @@ -1746,92 +2494,184 @@ def admin_panel(): save_data(data) return redirect(url_for('admin_panel')) - html = BASE_HTML.replace('{% block title %}{% endblock %}', 'Admin Panel - Content Hub') + ''' -{% block content %} - -
-

Admin Panel: All Posts

-
-
- - -
+ 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 %} +
-
- {% if posts %} - {% for post in posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-
-

{{ post['description'] }}

-

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

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

No posts found.

- {% endif %} + -
-{% block scripts %} - -{% endblock %} -{% endblock %} + + + ''' - return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), - NAV_HTML=NAV_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)) + 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)) if __name__ == '__main__': backup_thread = threading.Thread(target=periodic_backup, daemon=True)