diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,6 +1,4 @@ from flask import Flask, render_template_string, request, redirect, url_for, session, flash -from flask_caching import Cache -from flask_compress import Compress import json import os import logging @@ -9,26 +7,19 @@ import time from datetime import datetime, timedelta from huggingface_hub import HfApi, hf_hub_download from werkzeug.utils import secure_filename -from PIL import Image -import pyheif import random app = Flask(__name__) app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене DATA_FILE = 'data_adusis.json' -REPO_ID = "Eluza133/W1f9" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен для записи +REPO_ID = "Eluza133/A12d12s12" +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'}) -Compress(app) - -# Функции работы с базой данных -@cache.memoize(timeout=60) # Кэш на 60 секунд +# Функции для работы с данными def load_data(): try: download_db_from_hf() @@ -37,21 +28,13 @@ def load_data(): if not isinstance(data, dict): logging.warning("Данные не в формате dict, инициализация пустой базы") return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} - if 'posts' not in data: - data['posts'] = [] - if 'users' not in data: - data['users'] = {} - if 'general_chat' not in data: - data['general_chat'] = [] - if 'private_chats' not in data: - data['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 {} for user in data['users']: - if 'last_chat_visit' not in data['users'][user]: - data['users'][user]['last_chat_visit'] = '1970-01-01 00:00:00' - if 'last_private_visit' not in data['users'][user]: - data['users'][user]['last_private_visit'] = '1970-01-01 00:00:00' - if 'last_seen' not in data['users'][user]: - data['users'][user]['last_seen'] = '1970-01-01 00:00:00' + 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' logging.info("Данные успешно загружены") return data except Exception as e: @@ -64,7 +47,6 @@ def save_data(data): json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf() logging.info("Данные сохранены и загружены на HF") - cache.delete_memoized(load_data) # Очистка кэша после сохранения except Exception as e: logging.error(f"Ошибка при сохранении данных: {e}") raise @@ -127,95 +109,99 @@ 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 # Онлайн, если активен в последние 5 минут + return (datetime.now() - last_seen).total_seconds() < 300 def update_last_seen(data, username): if username in data['users']: data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') save_data(data) -# Исходный стиль -BASE_STYLE = ''' +# Критический CSS для быстрой загрузки +CRITICAL_CSS = ''' :root { - --primary: #ff4d6d; /* Vivid pink-red */ - --secondary: #00ddeb; /* Bright cyan */ - --accent: #8b5cf6; /* Electric purple */ - --background-light: #fef9f1; /* Soft cream */ - --background-dark: #1f1a44; /* Deep indigo */ + --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); --text-light: #2a1e5a; --text-dark: #e8e1ff; --shadow: 0 10px 30px rgba(0, 0, 0, 0.2); --glass-bg: rgba(255, 255, 255, 0.15); - --transition: all 0.4s ease-in-out; - --online: #34c759; /* Green for online */ - --offline: #ff3b30; /* Red for offline */ + --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.8; - transition: var(--transition); - overflow-x: hidden; + line-height: 1.6; } -body.dark { - background: var(--background-dark); - color: var(--text-dark); +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); } .sidebar { position: fixed; top: 0; left: 0; - width: 300px; + width: 280px; height: 100%; background: var(--glass-bg); backdrop-filter: blur(20px); - padding: 30px 20px; + padding: 20px; box-shadow: var(--shadow); z-index: 1000; transform: translateX(0); - transition: transform 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55); -} -.sidebar.hidden { - transform: translateX(-100%); + transition: var(--transition); + overflow-y: auto; } +.sidebar.hidden { transform: translateX(-100%); } .sidebar-header { display: flex; align-items: center; - gap: 15px; - margin-bottom: 40px; + gap: 10px; + margin-bottom: 30px; } .nav-brand { - font-size: 1.8em; - font-weight: 900; + font-size: 1.5em; + font-weight: 800; background: linear-gradient(135deg, var(--primary), var(--accent)); -webkit-background-clip: text; color: transparent; } .logo { - width: 40px; - height: 40px; - border-radius: 14px; + width: 36px; + height: 36px; + border-radius: 12px; box-shadow: var(--shadow); } .nav-links { display: flex; flex-direction: column; - gap: 18px; + gap: 12px; } .nav-link { display: flex; align-items: center; - gap: 15px; - padding: 15px 25px; + gap: 10px; + padding: 12px 20px; background: var(--card-bg-light); color: var(--text-light); text-decoration: none; - border-radius: 14px; - font-size: 1.1em; + border-radius: 12px; + font-size: 1em; font-weight: 600; transition: var(--transition); position: relative; @@ -228,90 +214,48 @@ body.dark .nav-link { transform: translateX(5px); background: var(--primary); color: white; - box-shadow: 0 6px 20px rgba(255, 77, 109, 0.4); + box-shadow: 0 4px 15px rgba(255, 77, 109, 0.4); } .nav-link .badge { position: absolute; - right: 15px; - background: var(--secondary); - color: white; - padding: 4px 8px; - border-radius: 12px; - font-size: 0.8em; - font-weight: 700; -} -.logout-btn { + right: 10px; background: var(--secondary); color: white; -} -.logout-btn:hover { - background: #00b8c5; -} -.menu-btn { - display: none; - font-size: 30px; - background: var(--glass-bg); - border: none; - color: var(--primary); - cursor: pointer; - position: fixed; - top: 20px; - left: 20px; - z-index: 1001; - padding: 12px; - border-radius: 50%; - box-shadow: var(--shadow); - transition: var(--transition); -} -.menu-btn:hover { - background: var(--primary); - color: white; -} -.container { - margin: 30px auto 30px 320px; - max-width: 1300px; - padding: 30px; - transition: var(--transition); + padding: 3px 6px; + border-radius: 10px; + font-size: 0.75em; } .btn { - padding: 14px 28px; + padding: 12px 24px; background: var(--primary); color: white; border: none; - border-radius: 14px; + border-radius: 12px; cursor: pointer; - font-size: 1.1em; - font-weight: 700; + font-size: 1em; + font-weight: 600; transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 10px; box-shadow: var(--shadow); } .btn:hover { - transform: scale(1.05); + transform: scale(1.03); background: #e6415f; - box-shadow: 0 8px 30px rgba(255, 77, 109, 0.5); } input, textarea, select { width: 100%; - padding: 14px; - margin: 14px 0; + padding: 12px; + margin: 10px 0; border: none; - border-radius: 14px; + border-radius: 12px; background: var(--glass-bg); color: var(--text-light); - font-size: 1.1em; + font-size: 1em; transition: var(--transition); - box-shadow: inset 0 3px 10px 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.2); - box-shadow: 0 0 0 4px var(--primary); + box-shadow: 0 0 0 3px var(--primary); } .modal { display: none; @@ -320,98 +264,79 @@ input:focus, textarea:focus, select:focus { left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.85); + background: rgba(0, 0, 0, 0.9); z-index: 2000; justify-content: center; align-items: center; - transition: opacity 0.4s ease; } .modal img, .modal video { - max-width: 95%; - max-height: 95%; - object-fit: contain; - border-radius: 20px; + max-width: 90%; + max-height: 90vh; + border-radius: 15px; box-shadow: var(--shadow); - animation: zoomIn 0.4s ease; } .theme-toggle { position: fixed; - top: 20px; - right: 20px; + top: 15px; + right: 15px; background: var(--glass-bg); border: none; - padding: 12px; + padding: 10px; border-radius: 50%; cursor: pointer; - font-size: 24px; + font-size: 20px; box-shadow: var(--shadow); transition: var(--transition); } .theme-toggle:hover { - transform: rotate(180deg); + transform: rotate(90deg); 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: 10px; - height: 10px; + 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.9); } - to { opacity: 1; transform: scale(1); } -} -@media (max-width: 900px) { +.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) { .sidebar { width: 100%; - max-width: 300px; + max-width: 280px; transform: translateX(-100%); } - .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; - } - .post-grid { - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - } -} -@media (max-width: 480px) { - .nav-brand { - font-size: 1.5em; - } - .nav-link { - font-size: 1em; - padding: 12px 20px; - } - .btn { - padding: 12px 20px; - font-size: 1em; - } + .sidebar.active { transform: translateX(0); } + .menu-btn { display: block; } + .container { margin: 60px 10px 20px; } + .theme-toggle { top: 60px; right: 10px; } } ''' NAV_HTML = ''' ''' -# Регистрация -@app.route('/register', methods=['GET', 'POST']) -def register(): - if request.method == 'POST': - username = request.form.get('username') - password = request.form.get('password') - data = load_data() - - if username in data['users']: - flash('Пользователь уже существует!') - return redirect(url_for('register')) - - data['users'][username] = { - 'password': password, - 'bio': '', - 'link': '', - 'avatar': None, - 'last_chat_visit': '1970-01-01 00:00:00', - 'last_private_visit': '1970-01-01 00:00:00', - 'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - save_data(data) - flash('Регистрация успешна! Пожалуйста, войдите.') - return redirect(url_for('login')) - - is_authenticated = 'username' in session - username = session.get('username', None) - data = load_data() - unread_count = get_unread_count(data, username) if is_authenticated else 0 - private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0 - user_count = len(data['users']) - is_online = is_user_online(data, username) if is_authenticated else False - - html = ''' +BASE_HTML = ''' @@ -474,87 +366,125 @@ def register(): - Register - Content Hub - - + {% block title %}{% endblock %} - Content Hub + + + - ''' + NAV_HTML + ''' + {{ NAV_HTML }}
-

Register

- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - -
- + {% block content %}{% endblock %} +
+ + {% block scripts %}{% endblock %} ''' - 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('/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 + ''' +{% block title %}Register{% endblock %} +{% block content %} + +
+

Register

+ {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+ + + +
+ +
+{% endblock %} +''' + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Вход @app.route('/login', methods=['GET', 'POST']) @@ -563,7 +493,6 @@ 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 @@ -571,7 +500,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: @@ -580,95 +509,56 @@ 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 = ''' - - - - - - - Login - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Login

- {% with messages = get_flashed_messages() %} - {% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - -
- -
- - - + + html = BASE_HTML + ''' +{% block title %}Login{% endblock %} +{% block content %} + +
+

Login

+ {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+ + + +
+ +
+{% endblock %} ''' - 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Выход @app.route('/logout') @@ -680,7 +570,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() @@ -698,7 +588,7 @@ def feed(): 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)[:20] # Только первые 20 постов + 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 @@ -709,180 +599,114 @@ 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 = ''' - - - - - - - - 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'] }}

-

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

-

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

- - {% endfor %} -
-
- - - - + html = BASE_HTML + ''' +{% block title %}Adusis - QoS, BBC, BNWO HUB{% endblock %} +{% 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, 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)) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), is_user_online=lambda u: is_user_online(data, u), **locals()) # Страница поста @app.route('/post/', methods=['GET', 'POST']) @@ -916,142 +740,72 @@ def post_page(post_id): 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 %} -
- - - - + html = BASE_HTML + ''' +{% block title %}{{ post['title'] }}{% endblock %} +{% 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, 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)) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), is_user_online=lambda u: is_user_online(data, u), **locals()) # Профиль пользователя @app.route('/profile', methods=['GET', 'POST']) @@ -1112,213 +866,112 @@ def profile(): save_data(data) 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 }}

- {% endif %} -

{{ bio }}

- {% if link %} -

{{ link }}

+ html = BASE_HTML + ''' +{% block title %}Profile - {{ username }}{% endblock %} +{% 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'] }} {% 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'] }}

-

{{ post['upload_date'] }}

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

You haven't uploaded any posts yet.

- {% endif %} +

{{ post['title'] }}

+ +

{{ post['description'] }}

+

{{ post['upload_date'] }}

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

No posts yet.

+ {% endif %} +
+{% block scripts %} + +{% endblock %} +{% endblock %} ''' - 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Профиль другого пол��зователя @app.route('/profile/') @@ -1345,187 +998,102 @@ def user_profile(username): profile_is_online = is_user_online(data, username) last_seen = user_data.get('last_seen', 'Never') - html = ''' - - - - - - - Profile - {{ username }} - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-
- {% 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 and username != session_username %} - Send Message + html = BASE_HTML + ''' +{% block title %}Profile - {{ username }}{% endblock %} +{% 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'] }} {% endif %} -
-
-
- {% for post in user_posts %} -
- - {% if post['type'] == 'video' %} - - {% else %} - {{ post['title'] }} - {% endif %} -

{{ post['title'] }}

-
-

{{ post['description'] }}

-

{{ post['upload_date'] }}

-
- {% endfor %} +

{{ post['title'] }}

+ +

{{ post['description'] }}

+

{{ post['upload_date'] }}

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

No posts yet.

+ {% endif %} +
+{% block scripts %} + +{% endblock %} +{% endblock %} ''' - return render_template_string(html, username=username, session_username=session_username, user_posts=user_posts, total_views=total_views, total_likes=total_likes, bio=bio, link=link, avatar=avatar, 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Загрузка контента @app.route('/upload', methods=['GET', 'POST']) def upload(): if 'username' not in session: - flash('Войдите, чтобы загрузить контент!') return redirect(url_for('login')) data = load_data() @@ -1541,37 +1109,16 @@ def upload(): title = request.form.get('title') description = request.form.get('description', '') file = request.files.get('file') - if not file or not title: - flash('Заполните все поля!') + flash('Title and file are required!') return redirect(url_for('upload')) - + filename = secure_filename(file.filename) temp_path = os.path.join('uploads', filename) os.makedirs('uploads', exist_ok=True) file.save(temp_path) - - # Определяем тип файла + file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', '.avi')) else 'photo' - if filename.lower().endswith(('.heic', '.heif')): - # Конверсия HEIC в JPEG - heif_file = pyheif.read(temp_path) - image = Image.frombytes( - heif_file.mode, - heif_file.size, - heif_file.data, - "raw", - heif_file.mode, - heif_file.stride, - ) - new_filename = filename.rsplit('.', 1)[0] + '.jpg' - temp_new_path = os.path.join('uploads', new_filename) - image.save(temp_new_path, "JPEG") - os.remove(temp_path) - temp_path = temp_new_path - filename = new_filename - file_type = 'photo' - api = HfApi() api.upload_file( path_or_fileobj=temp_path, @@ -1599,113 +1146,65 @@ def upload(): 'comments': [] }) save_data(data) - if os.path.exists(temp_path): os.remove(temp_path) return redirect(url_for('profile')) - html = ''' - - - - - - - Upload Content - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

Upload Content

-
- - - - -
-
-
-
-
- - - + xhr.send(formData); + }; + +{% endblock %} +{% endblock %} ''' - 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Общий чат @app.route('/chat', methods=['GET', 'POST']) @@ -1731,10 +1230,7 @@ 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 @@ -1744,26 +1240,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' - if filename.lower().endswith(('.heic', '.heif')): - heif_file = pyheif.read(temp_path) - image = Image.frombytes( - heif_file.mode, - heif_file.size, - heif_file.data, - "raw", - heif_file.mode, - heif_file.stride, - ) - new_filename = filename.rsplit('.', 1)[0] + '.jpg' - temp_new_path = os.path.join('uploads', new_filename) - image.save(temp_new_path, "JPEG") - os.remove(temp_path) - temp_path = temp_new_path - filename = new_filename - file_type = 'photo' - api = HfApi() file_path = f"chat_files/general/{filename}" api.upload_file( @@ -1787,226 +1264,93 @@ 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 = ''' - - - - - - - General Chat - Content Hub - - - - - - ''' + NAV_HTML + ''' - -
-

General Chat

-
- {% for message in chat_messages %} -
- {{ message['sender'] }} - - ({{ message['time'] }}): - {% if 'text' in message %} -

{{ message['text'] }}

+ html = BASE_HTML + ''' +{% block title %}General Chat{% endblock %} +{% 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 %} - {% if 'file' in message %} - {% if message['file_type'] == 'video' %} - - {% else %} - Chat file + {% endif %} + {% if 'post_id' in message %} + {% with post = posts|selectattr('id', 'equalto', message['post_id'])|first %} + {% if post %} +

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

{% 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 %} -
- + {% 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, - 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)) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), posts=data['posts'], user_posts=user_posts, + is_user_online=lambda u: is_user_online(data, u), **locals()) # Страница пользователей @app.route('/users', methods=['GET', 'POST']) @@ -2026,164 +1370,73 @@ def users(): if search_query: user_list = [u for u in user_list if search_query in u[0].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 %} -
+ html = BASE_HTML + ''' +{% block title %}Users{% endblock %} +{% block content %} + +
+

Users ({{ user_count }})

+
+
+ + +
+
+
+ {% for user, avatar, online in user_list %} + {% if user != username %} +
+ {% if avatar %} + {{ user }} Avatar + {% else %} +
+ {% endif %} + - {% endif %} - {% endfor %} -
+
+ {% endif %} + {% endfor %}
- - - +
+{% endblock %} ''' - 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) -# Страница сообщений (список приватных диалогов) +# Список приватных сообщений @app.route('/messages', methods=['GET']) def messages(): if 'username' not in session: @@ -2202,7 +1455,7 @@ def messages(): dialogs = {} for chat_key, messages in data['private_chats'].items(): user1, user2 = chat_key.split('_') - if username not in (user1, user2): # Пропускаем чаты, где пользователь не участвует + if username not in (user1, user2): continue other_user = user1 if user2 == username else user2 last_message = messages[-1] if messages else None @@ -2214,134 +1467,66 @@ def messages(): '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(30) }}{% elif 'file' in info.last_message %}File{% elif 'post_id' in info.last_message %}Post{% endif %}

- {% endif %} -
- {% if info.unread > 0 %} - {{ info.unread }} + html = BASE_HTML + ''' +{% block title %}Messages{% endblock %} +{% 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 %}

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

No messages yet.

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

No messages yet.

+ {% endif %}
- - - +
+{% endblock %} ''' - 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), **locals()) # Приватный чат @app.route('/chat/', methods=['GET', 'POST']) @@ -2362,7 +1547,6 @@ def private_chat(recipient): 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) @@ -2376,10 +1560,7 @@ 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 @@ -2389,26 +1570,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' - if filename.lower().endswith(('.heic', '.heif')): - heif_file = pyheif.read(temp_path) - image = Image.frombytes( - heif_file.mode, - heif_file.size, - heif_file.data, - "raw", - heif_file.mode, - heif_file.stride, - ) - new_filename = filename.rsplit('.', 1)[0] + '.jpg' - temp_new_path = os.path.join('uploads', new_filename) - image.save(temp_new_path, "JPEG") - os.remove(temp_path) - temp_path = temp_new_path - filename = new_filename - file_type = 'photo' - api = HfApi() file_path = f"chat_files/private/{chat_key}/{filename}" api.upload_file( @@ -2432,309 +1594,123 @@ 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 = ''' - - - - - - - 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 %} + html = BASE_HTML + ''' +{% block title %}Chat with {{ recipient }}{% endblock %} +{% 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 %} - {% if recipient_avatar %} - {{ recipient }} Avatar +
+ {% 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' %} + {% else %} -
+ Chat file {% endif %} {% endif %} -
- {{ message['sender'] }} - - {% if 'text' in message %} -

{{ message['text'] }}

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

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

{% 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'] }} -
+ {% endwith %} + {% endif %} + {{ message['time'] }}
- {% endfor %} -
-
- - - - -
- Back to Messages -
- + {% endfor %}
- - - +
+ + + + +
+ Back to Messages +
+{% block scripts %} + +{% endblock %} +{% endblock %} ''' 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) + import base64 + return render_template_string(html, CRITICAL_CSS=CRITICAL_CSS, FULL_CSS_BASE64=base64.b64encode(FULL_CSS.encode()).decode(), + NAV_HTML=NAV_HTML.format(**locals()), posts=data['posts'], user_posts=user_posts, + is_user_online=lambda u: is_user_online(data, u), **locals()) # Админ-панель @app.route('/admhosto', methods=['GET', 'POST']) @@ -2761,182 +1737,91 @@ def admin_panel(): 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'] }}

-

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

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

No posts found.

- {% endif %} -
+ html = BASE_HTML + ''' +{% block title %}Admin Panel{% endblock %} +{% block content %} + +
+

Admin Panel: All Posts

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