{% extends "base.html" %}

{% block content %}
<div class="container mt-4">
    <div class="row">

        <!-- Related Videos Section -->
<div class="col-md-3 order-md-2"> <!-- Left Side for Related Videos -->
    <h5>Related Videos</h5>
    {% for vid in random_videos %}
    <div class="related-video mb-3">
        <a href="/video/{{ vid.id }}" class="text-decoration-none text-dark">
            <img src="{{ url_for('static', filename='pre/' ~ vid.id ~ '.jpg') }}" class="img-fluid mb-1 related-video-img" alt="{{ vid.title }}">
            <p class="mt-3">{{ vid.title }}</p>
        </a>
        <div class="mt-2">
    <div class="avatar-container">
        <img src="{{ url_for('static', filename='avatars/' ~ vid.author.avatar) }}" alt="{{ vid.author.name }}" class="related-avatar me-2">
    </div>
    <div class="d-flex align-items-center">
        <a class="text-decoration-none text-dark h5 mb-0" href="{{ url_for('video', video_id=vid.id) }}"></a>
    </div>
    <p class="mb-1">
        <a class="text-decoration-none text-dark" href="{{ url_for('user', user_id=vid.author.id) }}">
            {{ vid.author.name }} {{ vid.author.surname }}
        </a>
    </p>
    <p class="text-muted mb-0">
        {{ vid.views }} {{ vid.views | decline_views }} -
        Загружено {{ vid.datetime | timesince }}
    </p>
</div>
    </div>
    {% endfor %}
</div>

<style>
    .related-avatar {
        width: 40px; /* Установите фиксированную ширину для аватара */
        height: 40px; /* Установите фиксированную высоту для аватара */
        object-fit: cover; /* Это гарантирует, что аватар сохраняет пропорции и не растягивается */
        /* Убедитесь, что этот стиль рассчитывается в рамках контейнера */
    }

    .avatar-container {
        width: 40px;
        height: 40px;
        border-radius: 50%; /* Закругляем аватар */
        overflow: hidden; /* Удаляет все, что выходит за рамки */
        display: inline-block; /* Этот стиль помогает избежать проблем с выравниванием */
    }
</style>

<style>
    .related-video-img {
        width: 300px;
        height: 170px;
        object-fit: cover; /* This will ensure the image maintains aspect ratio and is not stretched */
    }
</style>
<script type=text/javascript>
                function follow() {
                    var follow = document.getElementById("follow");
                    var unfollow = document.getElementById("unfollow");
                    var followers = document.getElementById("followers");
                    if ( follow.classList.contains('active') )
                    {
                        follow.classList.remove("active");
                        followers.innerHTML = parseInt(followers.innerText) - 1;
                        $.post('/api/not_follow/{{ current_video.author }}');
                    }
                    else
                    {
                        follow.classList.add("active");
                        followers.innerHTML = parseInt(followers.innerText) + 1;
                        $.post('/api/follow/{{ current_video.author }}');
                        if ( unfollow.classList.contains('active') )
                        {
                            unfollow.classList.remove("active");
                            followers.innerHTML = parseInt(followers.innerText) + 1;
                        }
                    }
                }
            </script>
            <script>
    function toggleLike(videoId) {
    const likeBtn = document.getElementById("like");
    const likesSpan = document.getElementById("likes");
    const isActive = likeBtn.classList.contains('active');

    const apiUrl = isActive ? `/api/not_like/${videoId}` : `/api/like/${videoId}`;

    fetch(apiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
    })
    .then(response => {
        if (!response.ok) {
            return response.json().then(err => { throw new Error(err.message); });
        }
        return response.json();
    })
    .then(data => {
        if (data.success) {
            likesSpan.innerText = data.new_like_count; // Update like count
            likeBtn.classList.toggle('active'); // Toggle button state
        } else {
            console.error('Failed to update like status:', data.message);
        }
    })
    .catch(error => {
        console.error('Error:', error);
    });
}
</script>
        <!-- Main Video Section -->
        <div class="col-md-9 order-md-1"> <!-- Right Side for Main Video -->
            <div class="video-container paused" data-volume-level="high">
                <img class="thumbnail-img">
                <div class="video-controls-container">
                    <!-- Control elements -->
                </div>
                <video width="100%" autoplay src="{{ video_src }}" controls autoplay width="408" >
                    <track kind="captions" srclang="en" src="assets/subtitles.vtt">
                </video>
            </div>

            <!-- Video Title and Details -->
            <h3 class="mt-3">{{ current_video.title }}</h3>
            <p>Загружено {{ current_video.datetime | timesince }} назад</p>
            {% if authenticated %}
    <button onclick="toggleLike('{{ current_video.id }}')"
        class="btn btn-outline-danger btn-sm {% if current_video.id in current_user.get_likes() %} active {% endif %}"
        type="button" id="like" {% if not authenticated or current_user.id == current_video.author %} disabled {% endif %}>


        {% if current_video.id in current_user.get_likes() %}
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                <g id="style=fill">
                    <g id="like">
                        <path id="Subtract" fill-rule="evenodd" clip-rule="evenodd" d="M15.9977 5.63891C16.2695 4.34931 15.433 3.00969 14.2102 2.59462C13.6171 2.37633 12.9892 2.4252 12.4662 2.60499C11.9449 2.78419 11.4461 3.12142 11.1369 3.58441L11.136 3.58573L7.49506 9.00272C8.05104 9.29585 8.43005 9.87954 8.43005 10.5518V21.3018H6.91003V21.3018H16.6801C18.2938 21.3018 19.2028 20.2977 19.8943 19.202C20.6524 18.0009 21.1453 16.7211 21.5116 15.5812C21.6808 15.0546 21.8252 14.5503 21.9547 14.0984L21.9863 13.9881C22.126 13.5007 22.2457 13.0904 22.366 12.7549C22.698 11.8292 22.5933 10.9072 22.067 10.2072C21.5476 9.5166 20.7005 9.15175 19.76 9.15175H15.76C15.6702 9.15175 15.6017 9.11544 15.5599 9.06803C15.5238 9.02716 15.4831 8.95058 15.502 8.81171L15.9977 5.63891Z" fill="#000000"/>
                        <path id="rec" d="M2.18005 10.6199C2.18005 10.03 2.62777 9.55176 3.18005 9.55176H6.68005C7.23234 9.55176 7.68005 10.03 7.68005 10.6199V21.3018H3.18005C2.62777 21.3018 2.18005 20.8235 2.18005 20.2336V10.6199Z" fill="#000000"/>
                    </g>
                </g>
            </svg>
        {% else %}

            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  <g fill-rule="evenodd" clip-rule="evenodd" fill="#000">
    <path d="M14.21 2.595c1.223.415 2.06 1.754 1.788 3.044l-.496 3.173c-.019.139.022.215.058.256.042.047.11.084.2.084h4c.94 0 1.787.365 2.307 1.055.526.7.63 1.622.299 2.548-.12.335-.24.746-.38 1.233l-.031.11a48 48 0 0 1-.443 1.483c-.367 1.14-.86 2.42-1.618 3.621-.691 1.096-1.6 2.1-3.214 2.1H6.66v-1.5h10.02c.821 0 1.338-.437 1.946-1.4.662-1.05 1.109-2.195 1.457-3.28.162-.502.3-.985.43-1.44l.031-.107c.137-.478.27-.937.41-1.326.188-.525.093-.903-.086-1.14-.186-.247-.548-.457-1.108-.457h-4c-1.043 0-1.909-.892-1.742-2.056v-.01l.505-3.224.005-.023c.11-.498-.243-1.138-.805-1.326l-.013-.004-.013-.005c-.203-.076-.47-.074-.743.02-.278.095-.48.257-.57.394l-.002.002-4.1 6.1-1.245-.837 4.099-6.097v-.002c.31-.463.809-.8 1.33-.979.523-.18 1.151-.229 1.744-.01"/>
    <path d="M1.43 10.552c0-.967.784-1.75 1.75-1.75h3.5c.967 0 1.75.783 1.75 1.75v10.75H3.18a1.75 1.75 0 0 1-1.75-1.75zm1.75-.25a.25.25 0 0 0-.25.25v9c0 .138.112.25.25.25h3.75v-9.25a.25.25 0 0 0-.25-.25z"/>
  </g>
</svg>
        {% endif %}

        <span id="likes">{{ current_video.likes }}</span>
    </button>
{% endif %}
<p>


            <div class="d-flex align-items-center mb-3">
            <a href="/user/{{ author.id }}" class="text-decoration-none text-dark">
                <img src="{{ avatar_url }}" alt="Avatar" class="img-fluid rounded-circle me-2" style="width:90px; height:90px;">
                </a>
                <div>
                    <a href="/user/{{ author.id }}" class="text-decoration-none text-dark">{{ author.name }} {{ author.surname }} </a>
                    <div>
                        <button onclick="follow()" class="btn btn-outline-danger btn-sm {% if subscription %} active {% endif %}" type="submit" name="follow" id="follow" {% if not authenticated or current_user.id == current_video.author %} disabled {% endif %}>
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-plus-fill" viewBox="0 0 16 16">
                            <path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
                            <path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
                        </svg>
                        <span id="followers">{{ author.followers }}</span>
                    </button>
                        <div class="col-auto">

                </div>
                    <script language="JavaScript" type="text/javascript" src="/static/js/jquery-1.2.6.min.js"></script>
<script language="JavaScript" type="text/javascript" src="/static/js/jquery-ui-personalized-1.5.2.packed.js"></script>
<script language="JavaScript" type="text/javascript" src="/static/js/sprinkle.js"></script>

                    </div>
                </div>
            </div>

            <p style="color: #808080">{{ current_video.description }}</p>
            {% if authenticated and (current_user.id |int == current_video.author |int or current_user.id == 1) %}
            <a href="{{ url_for('edit_video', video_id=current_video.id) }}" class="btn btn-warning">Edit Video</a>
            <form action="{{ url_for('delete_video', video_id=current_video.id) }}" style="display: inline;">
                <button type="submit" class="btn btn-danger">Delete Video</button>
            </form>

            {% endif %}
            <!-- Share Modal (Only for Author) -->
            {% if authenticated and current_user.id == author.id %}
                <!-- Share Modal -->
                <div class="modal fade" id="shareModal" tabindex="-1" aria-labelledby="shareModalLabel" aria-hidden="true">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header">
                                <h5 class="modal-title" id="shareModalLabel">Ссылка для общего доступа</h5>
                                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
                            </div>
                            <div class="modal-body">
                                <div class="mb-3">
                                    <label for="sharedLinkInput" class="form-label">Ссылка:</label>
                                    <input type="text" class="form-control" id="sharedLinkInput" readonly>
                                </div>
                                <button class="btn btn-secondary" id="copyLinkButton">Скопировать ссылку</button>
                            </div>
                            <div class="modal-footer">
                                <button type="button" class="btn btn-danger" data-bs-dismiss="modal">Закрыть</button>
                            </div>
                        </div>
                    </div>
                </div>
            {% endif %}
            <button id="shareBtn" class="btn btn-primary me-2">Поделиться</button>
            <script>
    // Share Functionality
    document.getElementById('shareBtn').addEventListener('click', () => {
        const currentURL = window.location.href;

        // Create a temporary input element to hold the URL
        const tempInput = document.createElement('input');
        tempInput.value = currentURL;
        document.body.appendChild(tempInput);

        // Select and copy the URL from the temporary input element
        tempInput.select();
        document.execCommand('copy'); // Legacy method for compatibility

        // Remove the temporary input element from the DOM
        document.body.removeChild(tempInput);

        // Show a confirmation alert
        alert('Ссылка скопирована!');
    });

    // You can add other JavaScript functions here, like liking comments or editing them.
</script>

  <script>
    document.addEventListener('DOMContentLoaded', function () {
      const shareButton = document.getElementById('shareButton');
      const shareModal = new bootstrap.Modal(document.getElementById('shareModal'));
      const sharedLinkInput = document.getElementById('sharedLinkInput');
      const copyLinkButton = document.getElementById('copyLinkButton');

      if (shareButton) {
        shareButton.addEventListener('click', function () {
          fetch("{{ url_for('share_video', video_id=current_video.id) }}", {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
                // Ensure CSRF token is included
            },
            body: JSON.stringify({})
          })
          .then(response => response.json())
          .then(data => {
            if (data.shared_url) {
              sharedLinkInput.value = data.shared_url;
              shareModal.show();
            } else {
              alert('Не удалось получить ссылку для общего доступа.');
            }
          })
          .catch(error => {
            console.error('Error:', error);
            alert('Произошла ошибка при генерации ссылки.');
          });
        });
      }

      if (copyLinkButton) {
        copyLinkButton.addEventListener('click', function () {
          sharedLinkInput.select();
          sharedLinkInput.setSelectionRange(0, 99999); // For mobile devices
          document.execCommand('copy');
          copyLinkButton.textContent = 'Скопировано!';
          setTimeout(() => {
            copyLinkButton.textContent = 'Скопировать ссылку';
          }, 2000);
        });
      }

      // Revoke Share Button
      const revokeShareButton = document.getElementById('revokeShareButton');
      const revokeModal = new bootstrap.Modal(document.getElementById('revokeModal'));

      if (revokeShareButton) {
        revokeShareButton.addEventListener('click', function () {
          revokeModal.show();
        });
      }
    });
  </script>
 <script>
        function like(videoId) {
            fetch(`/api/like/${videoId}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    const likesSpan = document.getElementById('likes');
                    likesSpan.innerText = data.new_like_count;
                } else {
                    console.error('Failed to like the video:', data.message);
                }
            })
            .catch(error => console.error('Error:', error));
        }
    </script>
<script>
    function likeComment(commentId) {
        fetch(`/api/like_comment/${commentId}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({}), // Optionally send payload if needed
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                const likeBtn = document.getElementById(`like-btn-${commentId}`);
                const likesCount = document.getElementById(`comment-likes-${commentId}`);
                likesCount.innerText = data.new_like_count;
                likeBtn.classList.toggle('active');
            } else {
                console.error(data.message);
            }
        })
        .catch(error => console.error('Error:', error));
    }
</script>
            <!-- Comments Section -->


           <div class="comments-section mt-5">
    <h4>Comments</h4>
    {% if authenticated and not current_user.is_blocked %}
    <form action="" method="post" id="comment-form">
        {{ form.hidden_tag() }}
        <div class="mb-3">
            {{ form.content(class="form-control", placeholder="Ваш комментарий...") }}
            {{ form.parent_id(value=0) }}
            {% for error in form.content.errors %}
            <div class="alert alert-danger" role="alert">
                {{ error }}
            </div>
            {% endfor %}
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    {% elif authenticated and current_user.is_blocked %}
    <p>Your account is blocked, you cannot comment.</p>
    {% else %}
    <p>Вы должны <a href="{{ url_for('login') }}">Войти</a> или <a href="{{ url_for('register') }}">Зарегистрироваться</a> чтобы писать комментарии.</p>
    {% endif %}

    <!-- Comments List -->
    {% for comment in comments %}
    <div class="media mb-3">
        <img src="{{ url_for('static', filename='avatars/' ~ comment.user.avatar) }}" alt="Avatar" class="mr-3 rounded-circle" style="width:50px; height:50px;">
        <div class="media-body">
            <h5 class="mt-0">
                <a href="{{ url_for('user', user_id=comment.user.id) }}" class="text-decoration-none text-dark">{{ comment.user.name }} {{ comment.user.surname }}</a>
            </h5>
            <p>{{ comment.content }}</p>
            <small class="text-muted">{{ comment.datetime.strftime('%Y-%m-%d %H:%M') }}</small>

            <!-- Like Button -->
            <div class="d-flex mt-2">
                <button onclick="likeComment({{ comment.id }})" class="btn btn-sm btn-outline-primary me-2" id="like-btn-{{ comment.id }}">
                    Like <span id="comment-likes-{{ comment.id }}">{{ comment.likes }}</span>
                </button>

                <!-- Edit and Delete Buttons for Author -->
                {% if current_user.is_authenticated and current_user.id == comment.user_id or current_user.id ==1 %}
                <a href="{{ url_for('edit_comment', comment_id=comment.id) }}" class="btn btn-sm btn-outline-warning me-2">Edit</a>
                <form action="{{ url_for('delete_comment', comment_id=comment.id) }}" method="post" style="display: inline;">
                    <button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
                </form>
                {% endif %}
            </div>
        </div>
    </div>
    {% endfor %}
</div>
        </div>

    </div>
</div>

<script>
    // JavaScript for actions such as copy, like, follow
    document.getElementById('copyBtn').addEventListener('click', () => {
        const currentURL = window.location.href;
        const tempInput = document.createElement('input');
        tempInput.value = currentURL;
        document.body.appendChild(tempInput);
        tempInput.select();
        document.execCommand('copy');
        document.body.removeChild(tempInput);
        alert('Ссылка скопирована!');
    });

    // Follow and like function implementations
</script>

<style>
    .video-container {
        width: 100%; /* Ensure full width within column */
    }

    .related-video img {
        width: 100%;
        border-radius: 4px;
    }
</style>

{% endblock %}
from flask_socketio import SocketIO
from flask import flash
import os
import json
from passlib.context import CryptContext
from PIL import Image
from flask import Flask, render_template, url_for, redirect, request, send_from_directory, flash, session
from flask_login import LoginManager, login_user, current_user, logout_user, UserMixin
from flask_restful import Api
from data.user_sid import UserSID
from sqlalchemy.exc import IntegrityError
from flask_sqlalchemy import SQLAlchemy
from data import db_session
from data.videos import Video
from data.users import User
from sqlalchemy.orm import joinedload
from data.bugs import Bug, BugComment, BugScreenshot
from data.login_form import LoginForm
from data.tracks import Track
from data.themes import Theme
from data.likes import Like
from data.albums import Album
from data.games import Game
from data.register_form import RegisterForm
from data.video_form import VideoForm
from data.users_resource import UsersResource, UserListResource
from data.videos_resource import VideosResource, VideoListResource
from data.likes_resource import LikeResource, NotLikeResource
from data.subscriptions_resource import FollowResource, NotFollowResource
from werkzeug.utils import secure_filename
from data.avatar_form import AvatarForm
from data.db_session import SqlAlchemyBase
from data.comments import Comment
from data.comment_form import CommentForm
from data.music import Music
from werkzeug.utils import secure_filename
from sqlalchemy import ForeignKey
from wtforms_components import ColorField
from sqlalchemy.orm import relationship
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
from sqlalchemy import func
from flask_migrate import Migrate
import uuid
import time
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime, timedelta
# from datetime import datetime
import pytz
from flask.json import JSONEncoder
from datetime import datetime, timedelta
import math

class CustomJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            # Adjust the datetime by +3 hours
            adjusted_time = obj + timedelta(hours=3)
            # Serialize to ISO 8601 format or any preferred format
            return adjusted_time.isoformat()

        # If you have other custom types, handle them here

        # For all other types, use the default serialization
        return super().default(obj)

moscow_tz = pytz.timezone('Europe/Moscow')
local_time = datetime.now(moscow_tz)
print(local_time)
app = Flask(__name__)
app.config['SECRET_KEY'] = 'yandexlyceum_secret_key'

api = Api(app)
socketio = SocketIO(app)
login_manager = LoginManager()
login_manager.init_app(app)

pwd_context = CryptContext(
    schemes=["pbkdf2_sha256"],
    default="pbkdf2_sha256",
    pbkdf2_sha256__default_rounds=30000
)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../instance/music.db'

db = SQLAlchemy(app)
migrate = Migrate(app, db)
# Set the folder to store avatars
AVATAR_UPLOAD_FOLDER = os.path.join(app.root_path, 'static', 'avatars')
# Allowed extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
BUG_SCREENSHOT_FOLDER = os.path.join(app.root_path, 'static', 'bug_screenshots')
ALLOWED_EXTENSIONS_BUG = {'png', 'jpg', 'jpeg', 'gif'}

os.makedirs(BUG_SCREENSHOT_FOLDER, exist_ok=True)
app.config['BANNER_UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads', 'banners')
os.makedirs(app.config['BANNER_UPLOAD_FOLDER'], exist_ok=True)  # Ensure the folder exists
app.config['BUG_SCREENSHOT_FOLDER'] = BUG_SCREENSHOT_FOLDER
app.config['AVATAR_UPLOAD_FOLDER'] = AVATAR_UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 300 * 1024 * 1024  # 2MB max upload sizes
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['COVER_FOLDER'] = 'static/covers'
app.config['ALLOWED_AUDIO_EXTENSIONS'] = {'mp3', 'wav', 'flac', 'ogg'}
app.config['ALLOWED_IMAGE_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}

# Создаем папки, если их нет
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['COVER_FOLDER'], exist_ok=True)
import tkinter as tk
import psutil
import time
import threading

# Модель для треков
from functools import wraps
from flask import abort
import random
from flask import send_file
import magic
import subprocess
from werkzeug.utils import secure_filename

UPLOAD_FOLDERA = 'uploadsvids'
PROCESSED_FOLDERA = 'processedvids'
ALLOWED_EXTENSIONS = {'mp4', 'mkv', 'avi', 'mov', 'flv'}

app.config['UPLOAD_FOLDERA'] = UPLOAD_FOLDERA
app.config['PROCESSED_FOLDERA'] = PROCESSED_FOLDERA

# Ensure that directories exist
os.makedirs(UPLOAD_FOLDERA, exist_ok=True)
os.makedirs(PROCESSED_FOLDERA, exist_ok=True)

# Define allowed extensions
# Define allowed file extensions as a global variable
ALLOWED_EXTENSIONS = {'mp4', 'mkv', 'avi', 'mov', 'flv'}
from mutagen import File as MutagenFile
app.json_encoder = CustomJSONEncoder
CLIENTS_DIR = 'data/clients'
CLIENTS_FILE = os.path.join(CLIENTS_DIR, 'clients.txt')

BLOCKED_IPS_DIR = 'data/blocked'
BLOCKED_IPS_FILE = os.path.join(BLOCKED_IPS_DIR, 'blocked_ips.txt')

# Create directories if they don't exist
os.makedirs(os.path.dirname(CLIENTS_FILE), exist_ok=True)
os.makedirs(os.path.dirname(BLOCKED_IPS_FILE), exist_ok=True)

# Ensure files exist
for file in [CLIENTS_FILE, BLOCKED_IPS_FILE]:
    if not os.path.exists(file):
        with open(file, 'w') as f:
            pass  # Create empty file

# Load blocked IPs into a set for quick access
blocked_ips = set()
def load_blocked_ips():
    global blocked_ips
    blocked_ips = set()
    try:
        with open(BLOCKED_IPS_FILE, 'r') as f:
            for line in f:
                ip = line.strip()
                if ip:
                    blocked_ips.add(ip)
    except FileNotFoundError:
        pass

load_blocked_ips()
@app.context_processor
def inject_helpers():
    return dict(get_client_ip=get_client_ip)
def get_client_ip():
    if request.headers.get('X-Forwarded-For'):
        # If behind a proxy
        ip = request.headers.get('X-Forwarded-For').split(',')[0].strip()
    else:
        ip = request.remote_addr
    return ip
# Helper functions to manage IP blocking
def block_ip(ip):
    global blocked_ips
    if ip not in blocked_ips:
        blocked_ips.add(ip)
        with open(BLOCKED_IPS_FILE, 'a') as f:
            f.write(f"{ip}\n")

def unblock_ip(ip):
    global blocked_ips
    if ip in blocked_ips:
        blocked_ips.remove(ip)
        with open(BLOCKED_IPS_FILE, 'w') as f:
            for b_ip in blocked_ips:
                f.write(f"{b_ip}\n")

def is_blocked(ip):
    return ip in blocked_ips

def get_client_ip():
    if request.headers.get('X-Forwarded-For'):
        # If behind a proxy
        ip = request.headers.get('X-Forwarded-For').split(',')[0].strip()
    else:
        ip = request.remote_addr
    return ip
from flask import g

# 1. before_request to check birthday
@app.before_request
def check_birthday_modal():
    g.show_birthday_modal = False  # Default flag

    if current_user.is_authenticated and current_user.birthday:
        # Adjust the current time to the desired timezone (e.g., Moscow)
        now = datetime.utcnow() + timedelta(hours=3)
        today = now.date()

        # Check if today is the user's birthday
        if (current_user.birthday.month, current_user.birthday.day) == (today.month, today.day):
            # Check if the modal has already been shown today
            if session.get('birthday_modal_shown_date') != today.isoformat():
                g.show_birthday_modal = True
                session['birthday_modal_shown_date'] = today.isoformat()

# 2. Context processor to inject the flag
@app.context_processor
def inject_birthday_modal():
    return {'show_birthday_modal': getattr(g, 'show_birthday_modal', False)}
# Logging and blocking logic before each request
@app.before_request
def log_and_block_ip():
    ip = get_client_ip()

    # Log the IP if not already logged
    with open(CLIENTS_FILE, 'a+') as f:
        f.seek(0)
        logged_ips = set(line.strip() for line in f)
        if ip not in logged_ips:
            f.write(f"{ip}\n")

    # Define admin routes to always allow access
    admin_routes = ['/admin/']

    # Define paths to exclude from blocking (e.g., '/blocked')
    exempt_paths = ['/blocked']

    # Allow access to admin routes and exempt paths
    if any(request.path.startswith(route) for route in admin_routes) or request.path in exempt_paths:
        return  # Allow admin and exempted routes access

    # Allow access to static files
    if request.path.startswith('/static/'):
        return

    # Check if the IP is blocked
    if is_blocked(ip):
        return redirect(url_for('blocked'))
# from apscheduler.schedulers.background import BackgroundScheduler
# from datetime import datetime, timedelta
#
# scheduler = BackgroundScheduler()
#
# def check_inactive_users():
#     """Mark users as offline if they haven't sent a heartbeat recently."""
#     threshold_time = datetime.utcnow() - timedelta(seconds=60)  # 60 seconds threshold
#     with db_sess.begin():
#         inactive_users = db_sess.query(User).filter(User.last_seen < threshold_time).all()
#         for user in inactive_users:
#             if user.is_online:  # Only change status if they are still marked as online
#                 user.is_online = False
#                 db_sess.commit()  # Commit the changes
#                 socketio.emit('user_status', {'user_id': user.id, 'status': 'offline'}, broadcast=True)
#                 print(f"User {user.id} marked as offline due to inactivity.")
#
# # Schedule the check to run every minute
# scheduler.add_job(check_inactive_users, 'interval', minutes=1)
# scheduler.start()
# import atexit
#
# atexit.register(lambda: scheduler.shutdown())
# import random
#
def get_random_videos(session, count=5):
    all_videos = session.query(Video).all()
    if len(all_videos) < count:
        return all_videos  # Return all if fewer than count available
    return random.sample(all_videos, count)  # Get random samples of the given count
# # Обработчик подключения
# @socketio.on('connect')
# def handle_connect():
#     """Handle user connection to set their online status."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 user.is_online = True  # Set online status to True
#                 db_sess.commit()  # Commit the changes
#                 emit('user_status', {'user_id': user.id, 'status': 'online'}, broadcast=True)
#                 print(f"User {user.id} is now online.")
#
#
# @socketio.on('disconnect')
# def handle_disconnect():
#     """Handle user disconnection to clean up user status."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 user.is_online = False  # Set online status to False
#                 db_sess.commit()  # Commit the changes
#                 emit('user_status', {'user_id': user.id, 'status': 'offline'}, broadcast=True)
#                 print(f"User {user.id} is now offline.")
#
#
# @socketio.on('heartbeat')
# def handle_heartbeat():
#     """Handle the heartbeat signal."""
#     if current_user.is_authenticated:
#         with db_sess.begin():
#             user = db_sess.query(User).get(current_user.id)
#             if user:
#                 # Update the last seen timestamp if needed
#                 user.last_seen = datetime.utcnow()  # Assuming you track the last seen time
#                 db_sess.commit()  # Commit the changes
#                 print(f"Heartbeat received for User {user.id}.")
# Route to display blocked page
@app.route('/blocked')
def blocked():
    return render_template('blocked.html')
@app.route('/paint')
def paint():
    return render_template('paint.html')
# Decorator to restrict access to admin users
def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not getattr(current_user, 'is_admin', False):
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

# Admin interface to manage client IPs
@app.route('/admin/clients', methods=['GET', 'POST'])
@admin_required
def admin_clients():
    # Read all logged client IPs
    try:
        with open(CLIENTS_FILE, 'r') as f:
            clients = {line.strip() for line in f}
    except FileNotFoundError:
        clients = set()

    # Prepare the list with block status
    client_list = []
    admin_ip = get_client_ip()  # Get admin's IP
    for ip in sorted(clients):
        client_list.append({
            'ip': ip,
            'blocked': is_blocked(ip)
        })

    if request.method == 'POST':
        action = request.form.get('action')
        ip = request.form.get('ip')
        if ip:
            if action == 'block':
                if ip != admin_ip:  # Prevent admin from blocking themselves
                    block_ip(ip)
                    flash(f"IP {ip} has been blocked.", 'success')
                else:
                    flash("You cannot block your own IP.", 'danger')
            elif action == 'unblock':
                unblock_ip(ip)
                flash(f"IP {ip} has been unblocked.", 'success')
            return redirect(url_for('admin_clients'))

    return render_template('admin_clients.html', clients=client_list, admin_ip=admin_ip)
def get_audio_info(filepath):
    try:
        audio = MutagenFile(filepath, easy=True)
        if audio is None:
            raise Exception("Unsupported file type or corrupted file.")

        audio_info = {
            "duration": int(audio.info.length),  # Duration in seconds
            "bitrate": audio.info.bitrate,  # Bitrate in bps
            "filetype": audio.mime[0] if audio.mime else "Unknown",
            "size": os.path.getsize(filepath),  # File size in bytes
            "metadata": dict(audio)  # Metadata tags
        }

        return audio_info

    except Exception as e:
        print(f"Error getting audio info: {e}")
        return None
from data.private_messages import PrivateMessage
from flask import jsonify
def should_redirect():
    try:
        with open('status.txt', 'r') as file:
            content = file.read().strip().lower()
            return content == 'yes'
    except FileNotFoundError:
        return False  # Handle the case where the file doesn't exist
import math
from datetime import datetime
def allowed_bug_screenshot(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS_BUG
def generate_shared_link(self):
        """Generates a unique shared link token."""
        self.shared_link = secrets.token_urlsafe(16)
def decline(number, one, two, five):
    """
    Возвращает правильную форму слова в зависимости от числа.

    Args:
        number (int): Число для склонения.
        one (str): Форма для числа 1 (например, "секунда").
        two (str): Форма для чисел 2-4 (например, "секунды").
        five (str): Форма для чисел 5 и более (например, "секунд").

    Returns:
        str: Правильная форма слова.
    """
    number = abs(number)
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return five
    if last == 1:
        return one
    if 2 <= last <= 4:
        return two
    return five

@app.route('/rules')
def rules():
    return render_template('rules.html')
@app.route('/test')
def test():
    return render_template('test.html')
from flask import jsonify  # Ensure this import is present
@app.route('/theme/publish', methods=['GET', 'POST'])
def publish_theme():
    if request.method == 'POST':
        name = request.form['name']
        description = request.form['description']
        primary_color = request.form['primary_color']
        secondary_color = request.form['secondary_color']
        background_color = request.form['background_color']

        # Create a new theme record
        new_theme = Theme(
            name=name,
            description=description,
            primary_color=primary_color,
            secondary_color=secondary_color,
            background_color=background_color,
            author_id=current_user.id
        )
        db_sess.add(new_theme)
        db_sess.commit()

        flash('Theme published successfully!', 'success')
        return redirect(url_for('theme_store'))

    return render_template('publish_theme.html')
@app.route('/theme/store', methods=['GET'])
def theme_store():
    themes = db_sess.query(Theme).all()  # Fetch all themes
    return render_template('theme_store.html', themes=themes)
@app.route('/select_theme/<int:theme_id>', methods=['POST'])
def select_theme(theme_id):
    theme = db_sess.query(Theme).get(theme_id)  # Get the theme by ID
    if theme is None:
        abort(404)  # If the theme does not exist, show a 404 error

    # Update the current user with the selected theme's information
    current_user.selected_theme_id = theme.id
    current_user.theme_primary_color = theme.primary_color
    current_user.theme_secondary_color = theme.secondary_color
    current_user.theme_background_color = theme.background_color
    db_sess.commit()  # Commit changes to the database

    flash('Theme selected successfully!', 'success')  # Notify the user of success
    return redirect(url_for('settings'))  # Redirect to settings or the appropriate page
@app.route('/games')
def games():
    db_sess = db_session.create_session()
    all_games = db_sess.query(Game).all()
    return render_template('games.html', games=all_games)

@app.route('/game_upload', methods=['GET', 'POST'])
def game_upload():
    if current_user.id != 1:
        return forbidden('')  # Ensure that only user with ID 1 can access

    if request.method == 'POST':
        name = request.form.get('name')
        iframe_code = request.form.get('iframe_code')
        image = request.files.get('image')
        slug = request.form.get('slug')

        if not name or not iframe_code or not image or not slug:
            flash('All fields are required!', 'danger')
            return redirect(request.url)

        # Save the image to the appropriate directory
        logo_folder = os.path.join('static', 'game_logos')
        os.makedirs(logo_folder, exist_ok=True)
        image_filename = secure_filename(image.filename)
        image_path = os.path.join(logo_folder, image_filename)
        image.save(image_path)

        new_game = Game(name=name, iframe_code=iframe_code, image=image_path, slug=slug, uploader_id=current_user.id)

        db_sess = db_session.create_session()
        db_sess.add(new_game)
        db_sess.commit()
        flash('Game uploaded successfully!', 'success')
        return redirect(url_for('games'))

    return render_template('game_upload.html')

@app.route('/game/<slug>')
def game(slug):
    db_sess = db_session.create_session()
    game = db_sess.query(Game).filter(Game.slug == slug).first()

    if not game:
        abort(404)  # This will trigger the 404 error handling in Flask

    uploader = db_sess.query(User).get(game.uploader_id)  # Get the uploader's information
    return render_template('game_detail.html', game=game, uploader=uploader)
@app.route('/delete_game/<int:game_id>', methods=['POST'])
def delete_game(game_id):
    if current_user.id != 1:
        return forbidden('')  # Ensure that only user with ID 1 can access

    db_sess = db_session.create_session()
    game = db_sess.query(Game).get(game_id)

    if game:
        db_sess.delete(game)
        db_sess.commit()
        flash('Game deleted successfully!', 'success')
    else:
        flash('Game not found.', 'danger')

    return redirect(url_for('games'))
@app.route('/admin_controls', methods=['GET', 'POST'])
def admin_controls():
    if not current_user.is_authenticated or current_user.id != 1:
        return forbidden('')  # Only allow user ID 1 (admin) to access this page

    if request.method == 'POST':
        # Adjust volume, bass, and treble based on form input
        volume = request.form.get('volume', type=float)
        bass = request.form.get('bass', type=float)
        treble = request.form.get('treble', type=float)

        # Emit an event to all connected clients
        socketio.emit('update_audio_settings', {
            'volume': volume,
            'bass': bass,
            'treble': treble
        }, namespace='/')  # Ensure the namespace matches your client-side setup

        # Return JSON response to the client
        return jsonify({'success': True, 'message': 'Audio settings updated successfully.'})

    return render_template('admin_controls.html')
@app.route('/bugs')
def bugs_list():
    bugs = db_sess.query(Bug).order_by(Bug.created_at.desc()).all()
    return render_template('bugs_list.html', bugs=bugs, current_user=current_user)
@app.route('/bugs/create', methods=['GET', 'POST'])
def create_bug():
    if request.method == 'POST':
        title = request.form.get('title')
        description = request.form.get('description')
        screenshots = request.files.getlist('screenshots')  # Получаем список файлов

        if not title or not description:
            flash('Название и описание обязательны.', 'danger')
            return redirect(request.url)

        new_bug = Bug(
            title=title,
            description=description,
            reporter_id=current_user.id
        )

        # Обработка нескольких скриншотов
        for screenshot in screenshots:
            if screenshot and allowed_bug_screenshot(screenshot.filename):
                filename = secure_filename(screenshot.filename)
                timestamp = datetime.utcnow().strftime('%Y%m%d%H%M%S')
                filename = f"{current_user.id}_{timestamp}_{filename}"
                screenshot.save(os.path.join(app.config['BUG_SCREENSHOT_FOLDER'], filename))
                bug_screenshot = BugScreenshot(filename=filename)
                new_bug.screenshots.append(bug_screenshot)

        db_sess.add(new_bug)
        db_sess.commit()
        flash('Баг успешно создан!', 'success')
        return redirect(url_for('bugs_list'))

    return render_template('create_bug.html', current_user=current_user)
@app.route('/bugs/<int:bug_id>', methods=['GET', 'POST'])
def bug_detail(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if request.method == 'POST' and current_user.is_authenticated:
        content = request.form.get('comment')
        if content:
            new_comment = BugComment(
                bug_id=bug.id,
                user_id=current_user.id,
                content=content
            )
            db_sess.add(new_comment)
            db_sess.commit()
            flash('Комментарий добавлен.', 'success')
            return redirect(url_for('bug_detail', bug_id=bug.id))
        else:
            flash('Комментарий не может быть пустым.', 'danger')

    return render_template('bug_detail.html', bug=bug, current_user=current_user)
@app.route('/bugs/<int:bug_id>/close', methods=['POST'])
def close_bug(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if current_user.id != 1 and not current_user.is_admin:
        abort(403)

    if bug.status == 'closed':
        flash('Баг уже закрыт.', 'info')
    else:
        bug.status = 'closed'
        db_sess.commit()
        flash('Баг закрыт.', 'success')

    return redirect(url_for('bug_detail', bug_id=bug.id))
@app.route('/bugs/<int:bug_id>/delete', methods=['POST'])
def delete_bug(bug_id):
    bug = db_sess.query(Bug).get(bug_id)
    if not bug:
        abort(404)

    if current_user.id != 1 and not current_user.is_admin:
        abort(403)

    # Удаление скриншота, если он существует
    if bug.screenshot:
        try:
            os.remove(os.path.join(app.config['BUG_SCREENSHOT_FOLDER'], bug.screenshot))
        except FileNotFoundError:
            pass

    db_sess.delete(bug)
    db_sess.commit()
    flash('Баг удален.', 'success')
    return redirect(url_for('bugs_list'))
def time_since(adjusted_upload_time):
    now = datetime.utcnow()
    diff = now - adjusted_upload_time
    seconds = diff.total_seconds()

    if seconds < 0:
        return "только что"  # If upload_time is in the future
    if seconds < 30:
        return "только что"
    elif seconds < 60:
        sec = math.floor(seconds)
        return f"{sec} {decline(sec, 'секунда', 'секунды', 'секунд')}"
    elif seconds < 3600:
        minutes = math.floor(seconds / 60)
        return f"{minutes} {decline(minutes, 'минута', 'минуты', 'минут')}"
    elif seconds < 86400:
        hours = math.floor(seconds / 3600)
        return f"{hours} {decline(hours, 'час', 'часа', 'часов')}"
    elif seconds < 604800:
        days = math.floor(seconds / 86400)
        return f"{days} {decline(days, 'день', 'дня', 'дней')}"
    elif seconds < 2592000:
        weeks = math.floor(seconds / 604800)
        return f"{weeks} {decline(weeks, 'неделя', 'недели', 'недель')}"
    elif seconds < 31536000:
        months = math.floor(seconds / 2592000)
        return f"{months} {decline(months, 'месяц', 'месяца', 'месяцев')}"
    elif seconds < 3153600000:
        years = math.floor(seconds / 31536000)
        return f"{years} {decline(years, 'год', 'года', 'лет')}"
    elif seconds < 3153600000000:
        centuries = math.floor(seconds / 3153600000)
        return f"{centuries} {decline(centuries, 'век', 'века', 'веков')}"
    else:
        millennia = math.floor(seconds / 3153600000000)
        return f"{millennia} {decline(millennia, 'тысячелетие', 'тысячелетия', 'тысячелетий')}"

# Jinja2 template filter
@app.template_filter('timesince')
def timesince_filter(upload_time):
    if not isinstance(upload_time, datetime):
        raise ValueError("upload_time должен быть объектом datetime")

    # The JSONEncoder already adjusts the time by +3 hours,
    # but if you need to use adjusted time elsewhere (like in templates),
    # you might need to adjust it here as well.
    adjusted_upload_time = upload_time + timedelta(hours=3)
    return time_since(adjusted_upload_time)

# This function will be executed before every request
@app.before_request
def check_file_and_redirect():
    # Exclude requests for static files and already redirected new_page
    if should_redirect() and not request.path.startswith('/static') and request.endpoint != 'pishu':
        return redirect(url_for('pishu'))
@app.route('/пишу код, сервер временно отключен.')

def pishu():
    params = {
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('underconstruct.html', **params)
@app.route('/video/<int:video_id>/share', methods=['POST'])
def share_video(video_id):
    db_sess = db_session.create_session()
    video = db_sess.query(Video).get(video_id)

    if video is None:
        flash('Видео не найдено.', 'danger')
        return redirect(url_for('index'))

    if video.author_id != current_user.id:
        flash('У вас нет прав для этого действия.', 'danger')
        return redirect(url_for('video', video_id=video_id))

    # Check if a shared link already exists
    if not video.shared_link:
        video.generate_shared_link()
        video.privacy = 2  # Set privacy to 'Shared' if not already
        try:
            db_sess.commit()
            flash('Ссылка для общего доступа создана.', 'success')
        except IntegrityError:
            db_sess.rollback()
            flash('Ошибка при создании ссылки. Попробуйте еще раз.', 'danger')
            return redirect(url_for('video', video_id=video_id))
    else:
        flash('Ссылка для общего доступа уже существует.', 'info')

    shared_url = url_for('video', video_id=video.id, shared=video.shared_link, _external=True)
    return jsonify({'shared_url': shared_url})  # Returning JSON for AJAX handling
@app.route('/faq')

def faq():
    params = {
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('underconstruct.html', **params)
@socketio.on('private_message')
def handle_private_message(data):
    """
    Handles sending of private messages.
    Expects data to contain:
    - 'receiver_id': ID of the user to send the message to
    - 'message': The message content
    """
    sender = current_user
    receiver_id = data.get('receiver_id')
    message_content = data.get('message')

    if not sender.is_authenticated:
        emit('error', {'message': 'Authentication required.'})
        return

    if not receiver_id or not message_content:
        emit('error', {'message': 'Invalid data.'})
        return

    receiver = db_sess.query(User).get(receiver_id)
    if not receiver:
        emit('error', {'message': 'Receiver not found.'})
        return

    # Save the message to the database
    private_message = PrivateMessage(
        sender_id=sender.id,
        receiver_id=receiver.id,
        content=message_content
    )
    db_sess.add(private_message)
    db_sess.commit()

    # Emit the message to the receiver's SIDs
    receiver_sids = [sid.sid for sid in receiver.sids]
    emit('receive_private_message', {
        'sender_id': sender.id,
        'sender_name': f"{sender.name} {sender.surname}",
        'message': message_content,
        'timestamp': private_message.timestamp.strftime('%Y-%m-%d %H:%M:%S')
    }, room=receiver_sids, namespace='/')

    # Optionally, emit the message back to the sender's other clients
    emit('receive_private_message', {
        'sender_id': sender.id,
        'sender_name': f"{sender.name} {sender.surname}",
        'message': message_content,
        'timestamp': private_message.timestamp.strftime('%Y-%m-%d %H:%M:%S')
    }, room=request.sid)

def convert_to_mp4(input_path, output_path):
    """
    Convert the input video file to MP4 format using FFmpeg.
    """
    try:
        subprocess.check_output([
            'ffmpeg', '-i', input_path, '-c:v', 'libx264',
            '-preset', 'fast', '-crf', '22',
            '-c:a', 'aac', '-b:a', '192k', output_path
        ])
    except subprocess.CalledProcessError as e:
        print(f"Failed to convert video. Error: {e.output}")
        raise
def allowed_file(filename):
    """
    Check if the file extension is in the allowed list.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# main2.py (continued)
# main2.py (continued)

@app.route('/private_chat')

def private_chat():
    users = db_sess.query(User).filter(User.id != current_user.id).all()
    params = {
        'title': 'Private Chat',
        'users': users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('private_chat.html', **params)

@app.route('/api/chat_history/<int:receiver_id>')

def chat_history(receiver_id):
    sender_id = current_user.id
    receiver = db_sess.query(User).get(receiver_id)
    if not receiver:
        return jsonify({'success': False, 'message': 'Receiver not found.'}), 404

    messages = db_sess.query(PrivateMessage).filter(
        or_(
            (PrivateMessage.sender_id == sender_id) & (PrivateMessage.receiver_id == receiver_id),
            (PrivateMessage.sender_id == receiver_id) & (PrivateMessage.receiver_id == sender_id)
        )
    ).order_by(PrivateMessage.timestamp.asc()).all()

    messages_data = []
    for msg in messages:
        messages_data.append({
            'sender_id': msg.sender_id,
            'sender_name': f"{msg.sender.name} {msg.sender.surname}",
            'message': msg.content,
            'timestamp': msg.timestamp.strftime('%Y-%m-%d %H:%M:%S')
        })

    return jsonify({'success': True, 'messages': messages_data})
@app.route('/mp4', methods=['GET', 'POST'])
def upload_mp4():
    if request.method == 'POST':
        if 'file' not in request.files:
            return "No file part"

        file = request.files['file']

        if file.filename == '':
            return "No selected file"

        # Call the allowed_file function with the filename only
        if file:
            filename = secure_filename(file.filename)
            file_path = os.path.join(app.config['UPLOAD_FOLDERA'], filename)
            file.save(file_path)

            mime = magic.Magic(mime=True)
            mime_type = mime.from_file(file_path)

            output_path = os.path.join(app.config['PROCESSED_FOLDERA'], f'{os.path.splitext(filename)[0]}.mp4')
            if mime_type != 'video/mp4':
                convert_to_mp4(file_path, output_path)
            else:
                convert_to_mp4(file_path, output_path)

            return redirect(url_for('uploaded_mp4', filename=os.path.basename(output_path)))

    return render_template('mp4.html')
@app.route('/tools', methods=['GET', 'POST'])
def tools():
    params = {
        'db_sess': db_sess,
        'User': User,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('tools.html', **params)

@app.route('/mp4s/<filename>')
def uploaded_mp4(filename):
    return send_from_directory(app.config['PROCESSED_FOLDERA'], filename)


UPLOAD_FOLDERI = 'static/uploadsimg'
CONVERTED_FOLDER = 'static/convertedimg'
os.makedirs(UPLOAD_FOLDERI, exist_ok=True)
os.makedirs(CONVERTED_FOLDER, exist_ok=True)

app.config['UPLOAD_FOLDERI'] = UPLOAD_FOLDERI

@app.route('/img2jpg', methods=['GET', 'POST'])
def convert_image():
    converted_file_url = None

    if request.method == 'POST':
        if 'image' not in request.files:
            return 'No file part', 400
        file = request.files['image']

        if file.filename == '':
            return 'No selected file', 400

        # Save the original file
        filename = secure_filename(file.filename)
        upload_path = os.path.join(app.config['UPLOAD_FOLDERI'], filename)
        file.save(upload_path)

        # Convert to JPG
        base_filename = os.path.splitext(filename)[0]
        jpg_filename = f"{base_filename}.jpg"
        converted_path = os.path.join(CONVERTED_FOLDER, jpg_filename)

        with Image.open(upload_path) as img:
            rgb_im = img.convert('RGB')  # Convert to RGB
            rgb_im.save(converted_path, 'JPEG')

        # Remove the original uploaded file
        os.remove(upload_path)

        # Get URL for downloading
        converted_file_url = url_for('static', filename=f'convertedimg/{jpg_filename}')

    return render_template('convert.html', converted_file_url=converted_file_url)
def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            abort(403)  # Запрещено
        return f(*args, **kwargs)
    return decorated_function

def owner_or_admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        track_id = kwargs.get('track_id')
        track = Track.query.get_or_404(track_id)
        if not current_user.is_authenticated:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@app.route('/delete_track/<int:track_id>', methods=['POST'])
def delete_track(track_id):
    track = db.session.query(Track).get(track_id)
    if not track:
        abort(404)  # Handle case where the track is not found

    try:
        # Delete audio file if it exists
        audio_path = os.path.join(app.config['UPLOAD_FOLDER'], track.audio_filename)
        if os.path.exists(audio_path):
            os.remove(audio_path)

        # List of default cover filenames
        default_covers = ['default.png']

        # Delete track cover if it's not a default cover
        if track.cover_filename and track.cover_filename not in default_covers:
            cover_path = os.path.join(app.config['COVER_FOLDER'], track.cover_filename)
            if os.path.exists(cover_path):
                os.remove(cover_path)

        # Delete the track record from the database
        db.session.delete(track)
        db.session.commit()

        flash('Трек успешно удалён.', 'success')
    except Exception as e:
        flash(f'Ошибка при удалении трека: {e}', 'danger')
        db.session.rollback()  # Rollback the session in case of error

    return redirect(url_for('tracks'))






# Проверка разрешенных расширений файлов
def allowed_file(filename, allowed_extensions):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in allowed_extensions

from sqlalchemy import func

@app.route('/audiosearch')
def audiosearch():
    query = request.args.get('query', '').strip()

    results = []
    if query:
        search_pattern = f"%{query.lower()}%"

        # Query to find matches in Track titles
        results = db.session.query(Track).filter(
            func.lower(Track.title).like(search_pattern)
        ).all()

    return render_template('search_results2.html', query=query, tracks=results)
# Генерация уникального идентификатора сессии
@app.before_request
def make_session_permanent():
    if 'session_id' not in session:
        session['session_id'] = str(uuid.uuid4())

# Страница загрузки трека
@app.route('/upload_audio', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        # Get form data
        title = request.form.get('title')
        audio_file = request.files.get('audio_file')
        cover_file = request.files.get('cover_file')
        album_id = request.form.get('album_id')
        new_album_title = request.form.get('new_album_title')
        new_album_artist = request.form.get('new_album_artist')
        new_album_cover = request.files.get('new_album_cover')

        # Check if form data contains album selection
        if album_id:
            album = db.session.query(Album).get(album_id)
            if not album:
                flash('Выбранный альбом не существует.', 'danger')
                return redirect(url_for('upload'))
        else:
            album_id = None

        # Validate required fields
        if not title or not audio_file:
            flash('Название и аудио файл обязательны для заполнения.', 'danger')
            return redirect(url_for('upload'))

        # Validate audio file type
        if not allowed_file(audio_file.filename, app.config['ALLOWED_AUDIO_EXTENSIONS']):
            flash('Неверный тип аудио файла.', 'danger')
            return redirect(url_for('upload'))

        # Handle new album creation
        if new_album_title and new_album_artist:
            # Handle new album cover
            new_album_cover_filename = 'default_album.png'  # Default if no cover is provided
            if new_album_cover and allowed_file(new_album_cover.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
                new_album_cover_filename = f"{uuid.uuid4()}_{secure_filename(new_album_cover.filename)}"
                new_album_cover_path = os.path.join(app.config['COVER_FOLDER'], new_album_cover_filename)
                new_album_cover.save(new_album_cover_path)

            # Create new album record
            new_album = Album(title=new_album_title, artist=new_album_artist, cover_filename=new_album_cover_filename)
            db.session.add(new_album)
            db.session.commit()
            album_id = new_album.id
            flash('Новый альбом успешно создан!', 'success')

        # Save audio file
        audio_filename = f"{uuid.uuid4()}_{secure_filename(audio_file.filename)}"
        audio_path = os.path.join(app.config['UPLOAD_FOLDER'], audio_filename)
        audio_file.save(audio_path)

        # Save or default track cover
        track_cover_filename = 'default.png'
        if cover_file and allowed_file(cover_file.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
            track_cover_filename = f"{uuid.uuid4()}_{secure_filename(cover_file.filename)}"
            cover_path = os.path.join(app.config['COVER_FOLDER'], track_cover_filename)
            cover_file.save(cover_path)

        # Create new track record
        new_track = Track(
            title=title,
            album_id=album_id,
            cover_filename=track_cover_filename,
            audio_filename=audio_filename,
            owner_id=current_user.id
        )

        db.session.add(new_track)
        db.session.commit()
        flash('Трек успешно загружен!', 'success')
        return redirect(url_for('track', track_id=new_track.id))

    # Retrieve all albums for the dropdown
    albums = db.session.query(Album).all()
    return render_template('upload.html', albums=albums)


# Маршрут для отображения всех альбомов
@app.route('/albums')
def albums():
    # Querying all albums ordered by upload_date in descending order
    all_albums = db.session.query(Album).order_by(Album.upload_date.desc()).all()
    all_tracks = db.session.query(Track).all()
    # Pass the queried album list to the template
    return render_template('albums.html', albums=all_albums, tracks=all_tracks)

# Маршрут для создания нового альбома
@app.route('/create_album', methods=['GET', 'POST'])
def create_album():
    # Только авторизованные пользователи могут создавать альбомы

    if request.method == 'POST':
        title = request.form.get('title')
        artist = request.form.get('artist')
        cover_file = request.files.get('cover_file')

        if not title or not artist:
            flash('Название и исполнитель обязательны для заполнения.', 'danger')
            return redirect(url_for('create_album'))

        # Обработка обложки альбома
        if cover_file and allowed_file(cover_file.filename, app.config['ALLOWED_IMAGE_EXTENSIONS']):
            cover_filename = str(uuid.uuid4()) + '_' + secure_filename(cover_file.filename)
            cover_path = os.path.join(app.config['COVER_FOLDER'], cover_filename)
            cover_file.save(cover_path)
        else:
            cover_filename = 'default_album.png'  # Используйте стандартную обложку

        # Создание нового альбома
        new_album = Album(title=title, artist=artist, cover_filename=cover_filename)
        db.session.add(new_album)
        db.session.commit()
        flash('Альбом успешно создан!', 'success')
        return redirect(url_for('albums'))

    return render_template('create_album.html')

# Маршрут для просмотра отдельного альбома с его треками
@app.route('/album/<int:album_id>')
def view_album(album_id):
    # Get the album by ID, or return 404 if not found
    album = db.session.query(Album).get_or_404(album_id)

    # Get all tracks associated with the album
    tracks = db.session.query(Track).filter_by(album_id=album_id).all()

    # Prepare parameters for rendering the template
    params = {
        'title': f"Album: {album.title} by {album.artist}",
        'album': album,
        'tracks': tracks,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    # Render the album view with the album and its tracks
    return render_template('view_album.html', **params)
# Страница со списком всех треков
@app.route('/record')
def record_audio():
    params = {
        'title': f"Записать аудио",
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('record.html', **params)
@app.route('/tracks')
def tracks():
    all_albums = db.session.query(Album).all()
    all_tracks = db.session.query(Track).filter(Track.album_id == None).all()
    random_albums = random.sample(all_albums, 3)
    params = {
        'user': user,
        'all_albums': all_albums
    }
    # Define a list of colors for default.png covers
    color_map = ["#FFB6C1", "#FFD700", "#ADFF2F", "#87CEFA", "#FF69B4", "#98FB98", "#FF6347", "#8A2BE2"]

    return render_template('tracks.html', tracks=all_tracks, color_map=color_map, random_albums=random_albums, albums=all_albums)
# Page for individual track
@app.route('/snake')
def snake():
    return render_template('snake.html')
import openai
@app.route('/chatgpt', methods=['GET', 'POST'])
def chatgpt():
    if request.method == 'POST':
        api_key = request.form.get('api_key')
        if api_key:
            session['api_key'] = api_key
            return redirect(url_for('chatwithgpt'))
        else:
            flash('Please enter a valid OpenAI API Key.', 'danger')
    return render_template('chatgpt.html')

# Route for the chat interface
@app.route('/chatwithgpt', methods=['GET', 'POST'])
def chatwithgpt():
    if 'api_key' not in session:
        flash('Please enter your OpenAI API Key first.', 'warning')
        return redirect(url_for('chatgpt'))

    response = None
    if request.method == 'POST':
        user_input = request.form.get('user_input')
        if user_input:
            # Set the OpenAI API Key from the session
            openai.api_key = session['api_key']
            try:
                # Make a request to OpenAI's API (using ChatGPT model)
                completion = openai.ChatCompletion.create(
                    model="gpt-3.5-turbo",
                    messages=[
                        {"role": "system", "content": "You are a helpful assistant."},
                        {"role": "user", "content": user_input}
                    ]
                )
                response = completion.choices[0].message['content']
            except openai.error.AuthenticationError:
                flash('Authentication failed. Please check your API Key.', 'danger')
                return redirect(url_for('chatgpt'))
            except Exception as e:
                flash(f'An error occurred: {str(e)}', 'danger')
        else:
            flash('Please enter a message to send.', 'warning')

    return render_template('chat.html', response=response)
@app.route('/track/<int:track_id>')
def track(track_id):
    track = db.session.query(Track).get(track_id)
    if track is None:
        return "Track not found", 404

    # Path to the audio file
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], track.audio_filename)

    # Get audio information
    audio_info = get_audio_info(filepath)

    # Check if the user has liked this track
    session_id = session.get('session_id')
    liked = db.session.query(Like).filter_by(session_id=session_id, track_id=track_id).first() is not None

    return render_template('track.html', track=track, liked=liked, audio_info=audio_info)

# Обработка лайков
@app.route('/like_music/<int:track_id>', methods=['POST'])
def like(track_id):
    session_id = session.get('session_id')
    if session_id is None:
        return redirect(url_for('tracks'))

    # Use db.session.query to retrieve the track
    track = db.session.query(Track).get(track_id)
    if not track:
        return "Track not found", 404

    # Use db.session.query for checking Likes
    existing_like = db.session.query(Like).filter_by(session_id=session_id, track_id=track_id).first()

    # Create a new Like if none exists
    if not existing_like:
        new_like = Like(session_id=session_id, track_id=track_id)
        track.likes += 1
        db.session.add(new_like)
        db.session.commit()

    return redirect(url_for('track', track_id=track_id))

# Page with recommendations
@app.route('/recommendations')
def recommendations():
    # Query top liked tracks using the session-based query
    top_liked = db.session.query(Track).order_by(Track.likes.desc()).limit(5).all()

    # Query newest tracks ordered by upload date
    newest = db.session.query(Track).order_by(Track.upload_date.desc()).limit(5).all()

    return render_template('recommendations.html', top_liked=top_liked, newest=newest)

# Отдача обложек и аудио файлов
#@app.route('/covers/<filename>')
#def cover(filename):
 #   return send_from_directory(app.config['COVER_FOLDER'], filename)

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

@app.route('/favicon.ico')
def favicon():
    """Возвращает кастомную иконку страницы в браузере"""
    return send_from_directory(os.path.join(app.root_path, 'static/img'),
                               'vhs.ico', mimetype='image/vnd.microsoft.icon')


@socketio.on('comment')
def handle_comment_event(username, video_title):
    message = f"{username} commented on your video '{video_title}'"
    socketio.emit('notify', {'message': message})


def like_video(video_id):
    # Assuming this function is called during a like action
    video = db_sess.query(Video).get(video_id)
    if video:
        message = f"Someone liked your video '{video.title}'"
        socketio.emit('notify', {'message': message}, room=video.author_id)  # send to video author
def subscribe_to_user(user_id):
    # Assuming this function is called during a subscribe action
    user = db.session.query(User).first()
    if user:
        message = "Someone subscribed to you!"
        socketio.emit('notify', {'message': message}, room=user.id)


def load_user(user_id):
    return db_sess.get(User, user_id)

@app.route('/upload_avatar', methods=['GET', 'POST'])
def upload_avatar():
    """Allows authenticated users to upload and customize their avatar."""
    form = AvatarForm()
    if request.method == 'POST':
        # Handling form submission and image saving
        if 'avatar' in request.files:
            avatar = request.files['avatar']
            filename = secure_filename(avatar.filename)
            file_ext = os.path.splitext(filename)[1].lower()
            avatar_filename = f"user_{current_user.id}{file_ext}"
            avatar_path = os.path.join(app.config['AVATAR_UPLOAD_FOLDER'], avatar_filename)

            # Save and process the avatar
            try:
                with Image.open(avatar) as img:
                    img = img.convert('RGB')
                    img.thumbnail((300, 300))
                    img.save(avatar_path, optimize=True, quality=85)

                # Update user's avatar in the database
                current_user.avatar = avatar_filename
                db_sess.commit()
                flash("Аватар успешно обновлен!", "success")
                return redirect(url_for('user', user_id=current_user.id))

            except Exception as e:
                flash('Загрузка не удалась. Пожалуйста, попробуйте снова.', 'danger')
                return redirect(url_for('upload_avatar'))

    return render_template('upload_avatar.html', form=form)

import signal
from flask import current_app, abort
import psutil

# Function to get system load
def get_system_load():
    return {
        'cpu_percent': psutil.cpu_percent(interval=1),
        'memory_percent': psutil.virtual_memory().percent,
        'disk_usage_percent': psutil.disk_usage('/').percent,
    }

@app.route('/admin/control_panel')
def admin_control_panel():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    system_load = get_system_load()

    params = {
        'title': 'Server Control Panel',
        'system_load': system_load,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    return render_template('admin_control_panel.html', **params)

@app.route('/admin/stop_server')
def stop_server():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    os.kill(os.getpid(), signal.SIGTERM)  # Gracefully terminate the server
    return "Server is stopping."

@app.route('/admin/stop_server_delayed')
def stop_server_delayed():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    def delayed_shutdown():
        time.sleep(300)  # Delay for 5 minutes
        os.kill(os.getpid(), signal.SIGTERM)  # Then terminate the server

    threading.Thread(target=delayed_shutdown).start()  # Run in a separate thread
    return "Server will stop in 5 minutes."

@app.route('/admin/reload')
def reload_server():
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')

    os.kill(os.getpid(), signal.SIGHUP)  # Reload the server
    return "Server is reloading."

@app.errorhandler(403)
def forbidden(e):
    """Возвращает кастомную страницу ошибки 403"""
    params = {
        'title': 'Доступ запрещен',
        'message': 'Похоже, у вас нет доступа к этой странице ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error1.html', **params), 403

def to_int(value):
    try:
        return int(value)
    except ValueError:
        return 0  # or handle as needed

app.jinja_env.filters['to_int'] = to_int

@app.errorhandler(404)
def page_not_found(e):
    """Возвращает кастомную страницу ошибки 404"""
    params = {
        'title': 'Страница не найдена',
        'message': 'Похоже, мы не можем найти нужную вам страницу ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error2.html', **params), 404


@app.errorhandler(405)
def method_not_allowed(e):
    """Возвращает кастомную страницу ошибки 405"""
    params = {
        'title': 'Метод не разрешен',
        'message': 'Похоже, этот метод не разрешен ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error3.html', **params), 405


@app.errorhandler(500)
def internal_server_error(e):
    """Возвращает кастомную страницу ошибки 500"""
    params = {
        'title': 'Ошибка сервера',
        'message': 'Похоже, на сервере произошла непредвиденная ошибка ¯\\_(ツ)_/¯',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('error4.html', **params), 500


# In your main application file (e.g., main2.py)

from sqlalchemy.orm import joinedload

@app.route('/')
@app.route('/index')
def index():
    """Returns the home page"""
    # Eagerly load the 'author' relationship to avoid additional queries in the template
    all_videos = db_sess.query(Video).options(joinedload(Video.author)).all()

    if len(all_videos) > 10:
        last_videos = all_videos[-10:][::-1]  # Get the last 10 videos
    else:
        last_videos = all_videos[::-1]  # Reverse the list

    best_videos = sorted(all_videos, key=lambda x: -x.likes)[:3]
    underrated_videos = [video for video in all_videos if video.likes == 0]

    if len(underrated_videos) > 9:
        underrated_videos = underrated_videos[-9:][::-1]
    else:
        underrated_videos = underrated_videos[::-1]

    params = {
        'title': 'YouBox',
        'last_videos': last_videos,
        'best_videos': best_videos,
        'underrated_videos': underrated_videos,
        'user': user,
        'db_sess': db_sess,
        'User': User,
        'Video': Video,
        'authenticated': current_user.is_authenticated,
        'all_videos': all_videos,
        'current_user': current_user
    }
    return render_template('index.html', **params)


@login_manager.user_loader
def load_user(user_id):
    """Возвращает пользователя"""
    return db_sess.query(User).get(user_id)


@app.route('/login', methods=['GET', 'POST'])
def login():
    """Возвращает страницу авторизации и авторизует пользователя"""
    if current_user.is_authenticated:
        return forbidden('')
    form = LoginForm()
    params = {
        'title': 'Вход в аакаунт - YouBox',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        user = db_sess.query(User).filter(User.login == form.login.data).first()
        if user and pwd_context.verify(form.password.data, user.password):
            login_user(user, remember=form.remember_me.data)
            return redirect('/')
        return render_template('login.html',
                               message='Неправильный логин или пароль',
                               form=form, **params)
    return render_template('login.html', form=form, **params)

from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user

@app.route('/edit_comment/<int:comment_id>', methods=['GET', 'POST'])

def edit_comment(comment_id):
    # Fetch the comment from the database using comment_id
    comment = db_sess.query(Comment).filter(Comment.id == comment_id).first()

    # Ensure the comment exists and the current_user is the author
    if comment is None or comment.user_id != current_user.id:
        flash('Comment not found or you do not have permission to edit it.', 'danger')
        return redirect(url_for('video', video_id=comment.video_id))  # Redirect to the video's page

    if request.method == 'POST':
        new_content = request.form.get('content')
        if new_content:
            comment.content = new_content
            db_sess.commit()
            flash('Коментарий успешно изменен!', 'success')
        else:
            flash('Нет изменений в комментарие.', 'danger')
        return redirect(url_for('video', video_id=comment.video_id))

    return render_template('edit_comment.html', comment=comment)

@app.route('/register', methods=['GET', 'POST'])
def register():
    """Возвращает страницу регистрации и регистрирует пользователя"""
    if current_user.is_authenticated:
        return forbidden('')
    form = RegisterForm()
    params = {
        'title': 'Регистрация',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        user = db_sess.query(User).filter(User.login == form.login.data).all()
        if user:
            return render_template('register.html',
                                   message='Пользователь с таким ником уже существует',
                                   form=form, **params)
        if form.password.data != form.repeat_password.data:
            return render_template('register.html',
                                   message='Пароли не совпадают',
                                   form=form, **params)
        user = User()
        user.login = form.login.data
        user.password = pwd_context.hash(form.password.data)
        user.name = form.name.data
        user.surname = form.surname.data
        db_sess.add(user)
        db_sess.commit()
        login_user(user, remember=form.remember_me.data)
        return redirect('/')
    return render_template('register.html', form=form, **params)
from flask import jsonify, request, current_app

@app.route('/api/like_comment/<int:comment_id>', methods=['POST']) # Ensure the user is logged in
def like_comment(comment_id):
    # Logic to handle liking a comment
    comment = db_sess.query(Comment).filter_by(id=comment_id).first()
    if not comment:
        return jsonify({'success': False, 'message': 'Comment not found.'}), 404

    # Example logic: increment the like count
    comment.likes += 1
    db_sess.commit()

    return jsonify({'success': True, 'new_like_count': comment.likes})

@app.route('/logout')
def logout():
    if current_user.is_authenticated:
        # Create a new session for this operation
        new_session = db_session.create_session()
        user = new_session.query(User).get(current_user.id)
        if user:
            user.is_online = False
            new_session.commit()
        logout_user()
    return redirect('/')
@app.route('/api/pin_comment/<int:comment_id>', methods=['POST'])
def pin_comment(comment_id):
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return {'error': 'Comment not found'}, 404
    video = db_sess.query(Video).get(comment.video_id)

    # Сбрасываем все закрепленные комментарии для данного видео
    db_sess.query(Comment).filter(Comment.video_id == video.id).update({'pinned': False})
    # Закрепляем выбранный комментарий
    comment.pinned = True
    db_sess.commit()
    return {'success': True}
@app.route('/delete_comment/<int:comment_id>', methods=['POST'])
def delete_comment(comment_id):
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        flash('Комментарий не найден.', 'danger')
        return redirect(request.referrer)
    if not current_user.is_authenticated or current_user.id != comment.user_id:
        flash('Это не твой Комментарий, пошел нафиг.', 'danger')
        return redirect(request.referrer)
    db_sess.delete(comment)
    db_sess.commit()
    flash('Комментарий удален.', 'success')
    return redirect(request.referrer)
from flask import abort
from data.video_views import VideoView
@app.route('/video/<int:video_id>', methods=['GET', 'POST'])
def video(video_id):
    db_sess = db_session.create_session()
    current_video = db_sess.query(Video).get(video_id)
    if current_video is None:
        return page_not_found('')
    random_videos = get_random_videos(db_sess, count=12)

    # Access Control Logic
    shared = request.args.get('shared', type=str)
    # By default, access is denied
    access_granted = True

    if current_video.privacy == 1:  # Public
        access_granted = True
    elif current_video.privacy == 2:  # Shared
        if shared and shared == current_video.shared_link:
            access_granted = True
    elif current_video.privacy == 3:  # Private
        if current_user.is_authenticated and current_user.id == current_video.author_id:
            access_granted = True

    if not access_granted:
        flash('У вас нет доступа к этому видео.', 'danger')
        return redirect(url_for('index'))

    # Proceed with existing logic
    comments_enabled = current_video.comments_enabled
    author = current_video.author

    # Efficient retrieval of last 10 videos
    last_videos = db_sess.query(Video)\
        .order_by(Video.datetime.desc())\
        .limit(10).all()

    like = False
    subscription = False

    if current_user.is_authenticated:
        # Handle view counting
        try:
            new_view = VideoView(user_id=current_user.id, video_id=video_id)
            db_sess.add(new_view)
            current_video.views += 1
            db_sess.commit()
        except IntegrityError:
            db_sess.rollback()  # View already exists

        # Handle likes and subscriptions using association tables
        likes = json.loads(current_user.likes) if current_user.likes else []
        subscriptions = json.loads(current_user.subscriptions) if current_user.subscriptions else []
        like = video_id in likes
        subscription = current_video.author_id in subscriptions

    # Handle comment form
    form = CommentForm()
    if comments_enabled and form.validate_on_submit():
        if current_user.is_authenticated and not current_user.is_blocked:
            comment = Comment(
                content=form.content.data,
                user_id=current_user.id,
                video_id=video_id,
                parent_id=form.parent_id.data or None
            )
            db_sess.add(comment)
            db_sess.commit()
            flash('Комментарий добавлен!', 'success')
            return redirect(url_for('video', video_id=video_id))
        else:
            flash('Вы не можете оставить комментарий, вы заблокированы.', 'danger')
            return redirect(url_for('video', video_id=video_id))

    # Retrieve comments with eager loading
    comments = db_sess.query(Comment).filter(Comment.video_id == video_id).options(
        joinedload(Comment.user),
        joinedload(Comment.parent).joinedload(Comment.user),
        joinedload(Comment.replies).joinedload(Comment.user)
    ).order_by(Comment.datetime.desc()).all()

    # Build root comments
    comments_by_id = {comment.id: comment for comment in comments}
    root_comments = []
    for comment in comments:
        if comment.parent_id is None:
            root_comments.append(comment)
        else:
            parent = comments_by_id.get(comment.parent_id)
            if parent:
                if not hasattr(parent, 'replies_sorted'):
                    parent.replies_sorted = []
                parent.replies_sorted.append(comment)

    params = {
        'title': f"{current_video.title} - {author.name} {author.surname}",
        'current_video': current_video,
        'video_src': url_for("static", filename=f'vid/{video_id}.mp4'),
        'author': author,
        'like': like,
        'random_videos': random_videos,  # Pass random videos to the template
        'subscription': subscription,
        'comments': root_comments,  # Root comments only
        'last_videos': last_videos,
        'form': form,
        'avatar_url': url_for('static', filename=f'avatars/{author.avatar}'),
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
    }
    return render_template('video.html', **params)



@app.route('/add_video', methods=['GET', 'POST'])
def add_video():
    if not current_user.is_authenticated:
        return forbidden('')  # Ensure you have a `forbidden` handler defined

    form = VideoForm()
    params = {
        'title': 'Добавление видео',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    if current_user.is_blocked:
        flash('Ваш аккаунт заблокирован и вы не можете загружать видео.', 'danger')
        return render_template('add_video.html', form=form, **params)

    if form.validate_on_submit():
        video_file = form.video.data
        preview_file = form.preview.data
        datetime_input = form.datetime.data  # Получаем выбранную дату

        # Установка даты загрузки
        video_datetime = datetime_input if datetime_input else datetime.utcnow()

        # Validate video file format
        if not video_file.filename.lower().endswith('.mp4'):
            flash('Данный формат видео не поддерживается. Пожалуйста, загрузите видео в формате mp4.', 'danger')
            return render_template('add_video.html', form=form, **params)

        # Validate preview image format
        if not preview_file.filename.lower().endswith('.jpg'):
            flash('Данный формат обложки не поддерживается. Пожалуйста, загрузите изображение в формате jpg.', 'danger')
            return render_template('add_video.html', form=form, **params)

        try:
            # Create a new Video object
            video = Video(
                title=form.title.data,
                description='<br>'.join(form.description.data.split('\n')),
                author_id=current_user.id,
                comments_enabled=form.comments_enabled.data,
                datetime=video_datetime  # Assuming your Video model has this field
            )
            db_sess.add(video)
            db_sess.commit()

            # Save uploaded files
            video_path = os.path.join('static/vid', f'{video.id}.mp4')
            preview_path = os.path.join('static/pre', f'{video.id}.jpg')
            video_file.save(video_path)
            preview_file.save(preview_path)

            flash('Видео успешно загружено!', 'success')
            return redirect('/')

        except Exception as e:
            db_sess.rollback()
            flash(f'Произошла ошибка при загрузке видео: {e}', 'danger')
            return render_template('add_video.html', form=form, **params)

    return render_template('add_video.html', form=form, **params)


@app.route('/edit_video/<int:video_id>', methods=['GET', 'POST'])
def edit_video(video_id):
    """
    Возвращает страницу редактирования видео
    если пользователь авторизован и является автором,
    иначе доступ запрщен
    """
    form = VideoForm()
    video = db_sess.query(Video).get(video_id)
    if not current_user.is_authenticated:
        return forbidden('')
    if not current_user.id != video.author:
        return forbidden('')

    params = {
        'title': 'Редактирование видео',
        'video': video,
        'description': '\n'.join(video.description.split('<br>')),
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    if form.validate_on_submit():
        video_file = request.files['video']
        if not video_file.filename.lower().endswith('.mp4'):
            return render_template('edit_video.html',
                                   message='Данный формат видео не поддерживается',
                                   form=form, **params)
        preview_file = request.files['preview']
        if not preview_file.filename.lower().endswith('.jpg'):
            return render_template('edit_video.html',
                                   message='Данный формат обложки не поддерживается',
                                   form=form, **params)
        video.title = form.title.data
        video.description = '<br>'.join(form.description.data.split('\n'))
        db_sess.commit()
        video_file.save(os.path.join('static/vid', f'{video.id}.mp4'))
        preview_file.save(os.path.join('static/pre', f'{video.id}.jpg'))
        with Image.open(f'static/pre/{video.id}.jpg') as preview_file:
            width, height = preview_file.size
            if width >= height:
                new_width = height * 16 // 9
                delta = (width - new_width) // 2
                preview_file = preview_file.crop((delta, 0, new_width + delta, height))
            else:
                new_height = width * 9 // 16
                delta = (height - new_height) // 2
                preview_file = preview_file.crop((0, delta, width, new_height + delta))
            preview_file.save(f'static/pre/{video.id}.jpg')
        return redirect('/')
    return render_template('edit_video.html', form=form, **params)


@app.route('/delete_video/<int:video_id>')
def delete_video(video_id):
    video = db_sess.query(Video).get(video_id)
    if not video:
        return page_not_found('Video not found.')


    # No need for explicit comment deletion; should cascade
    db_sess.delete(video)
    db_sess.commit()

    # Remove video file and preview
    try:
        os.remove(os.path.join('static/vid', f'{video.id}.mp4'))
        os.remove(os.path.join('static/pre', f'{video.id}.jpg'))
    except FileNotFoundError:
        pass  # Handle if the files do not exist

    return redirect(f'/')


@app.route('/user/<int:user_id>')
def user(user_id):
    """Возвращает страницу выбранного пользователя с его видео"""
    user = db_sess.query(User).get(user_id)
    if user is None:
        return page_not_found('')

    # Получаем все видео пользователя
    videos = db_sess.query(Video).filter(Video.author_id == user_id).all()[::-1]
    is_birthday = False
    if user.birthday:
        today = datetime.utcnow()
        # Add 3 hours if you need to match a different timezone (e.g., Moscow)
        today_with_offset = today + timedelta(hours=3)
        if (user.birthday.month, user.birthday.day) == (today_with_offset.month, today_with_offset.day):
            is_birthday = True
    # Вычисляем количество видео
    video_count = len(videos)
    # Альтернативно, для оптимизации можно использовать:
    # video_count = db_sess.query(Video).filter(Video.author == user_id).count()

    subscription = False
    if current_user.is_authenticated:
        subscriptions = json.loads(current_user.subscriptions)
        if user_id in subscriptions:
            subscription = True
    print(f"Is user {user_id} subscribed by current user? {'Yes' if subscription else 'No'}")
    # Пагинация видео (разбивка на группы по 3 видео)
    paginated_videos = [videos[i * 3:i * 3 + 3] for i in range(len(videos) // 3)] + [videos[(len(videos) // 3) * 3:]]

    # Добавляем все необходимые параметры, включая video_count
    params = {
        'title': f'{user.name} {user.surname} - Youbox',
        'user': user,
        'videos': paginated_videos,
        'subscription': subscription,
        'empty': True if len(videos) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'video_count': video_count,  # Передаём количество видео
        'is_birthday': is_birthday  # Pass if today is their birthday
    }
    return render_template('user.html', **params)
@app.route('/delete_user', methods=['GET', 'POST'])
def delete_user():
    """Handles user deletion with confirmation."""
    if not current_user.is_authenticated:
        return forbidden('')
    if request.method == 'POST':
        # Check for confirmation
        if 'confirm_delete' in request.form:
            # Delete user's videos (assuming you want to cascade deletion)


            # Delete user's comments
            for comment in current_user.comments:
                db_sess.delete(comment)

            # Delete the user
            db_sess.delete(current_user)
            db_sess.commit()

            flash('Ваш аккаунт успешно удален.', 'success')
            return redirect(url_for('index'))  # Redirect to homepage
        else:
            flash('Удаление аккаунта отменено.', 'info')
            return redirect(url_for('settings'))  # Redirect back to settings

    # GET request - show confirmation page
    return render_template('delete_user.html', title='Delete Account')
@app.route('/settings')
def settings():


    if not current_user.is_authenticated:
        return forbidden('')
    params = {
        'title': f'Настройки',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('settings.html', **params)
def decline_subscription(number):
    """
    Возвращает правильную форму слова "подписчик" в зависимости от числа.
    Например:
        1 -> подписчик
        2 -> подписчика
        5 -> подписчиков
    """
    number = abs(number)  # Убираем знак, если число отрицательное
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return "подписчиков"
    if last == 1:
        return "подписчик"
    if 2 <= last <= 4:
        return "подписчика"
    return "подписчиков"
def decline_views(number):
    """
    Возвращает правильную форму слова "подписчик" в зависимости от числа.
    Например:
        1 -> подписчик
        2 -> подписчика
        5 -> подписчиков
    """
    number = abs(number)  # Убираем знак, если число отрицательное
    last_two = number % 100
    last = number % 10

    if 11 <= last_two <= 14:
        return "просмотров"
    if last == 1:
        return "просмотр"
    if 2 <= last <= 4:
        return "просмотра"
    return "просмотров"
# Регистрация фильтра в Jinja2
app.jinja_env.filters['decline_subscription'] = decline_subscription
app.jinja_env.filters['decline_views'] = decline_views
from data.users import User
from sqlalchemy import update

@app.route('/admin/users', methods=['GET', 'POST'])
def admin_users():
    """Административная страница для управления флажками, блокировками и статусом админа пользователей"""
    if not current_user.is_authenticated or not current_user.is_admin:
        return forbidden('')  # Только администратор имеет доступ

    users = db_sess.query(User).all()

    if request.method == 'POST':
        # Обработка изменений флажков, блокировок и статуса админа
        for user in users:
            if user.id == 1:
                continue  # Не изменять статус администратора

            # Обработка флажков is_checked
            is_checked = request.form.get(f'checked_{user.id}', 'off') == 'on'
            if user.is_checked != is_checked:
                user.is_checked = is_checked
                db_sess.add(user)

            # Обработка блокировок is_blocked
            is_blocked = request.form.get(f'blocked_{user.id}', 'off') == 'on'
            if user.is_blocked != is_blocked:
                user.is_blocked = is_blocked
                db_sess.add(user)

            is_tester = request.form.get(f'is_tester_{user.id}', 'off') == 'on'
            if user.is_tester != is_tester:
                user.is_tester = is_tester
                db_sess.add(user)

            # Обработка статуса администратора is_admin
            is_admin = request.form.get(f'is_admin_{user.id}', 'off') == 'on'
            if user.is_admin != is_admin:
                user.is_admin = is_admin
                db_sess.add(user)
        db_sess.commit()
        flash('Изменения успешно сохранены.', 'success')
        return redirect(url_for('admin_users'))

    params = {
        'title': 'Администрирование пользователей',
        'users': users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('admin_users.html', **params)
@app.route('/feed')
def feed():
    """
    Возвращает страницу с лентой видео от людей,
    на которых подписан пользователь.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    subscriptions = json.loads(current_user.subscriptions)
    videos = []
    for i in subscriptions:
        videos += db_sess.query(Video).filter(Video.author == i).all()[::-1][:12]
    new_videos = sorted(videos, key=lambda x: x.datetime)[::-1]
    new_videos = [
        {
            'video': i,
            'author': db_sess.query(User).get(i.author)
        }
        for i in new_videos
    ]
    params = {
        'title': f'Лента',
        'new_videos': [new_videos[i * 3:i * 3 + 3] for i in range(len(new_videos) // 3)] +
                      [new_videos[(len(new_videos) // 3) * 3:]],
        'empty': True if len(new_videos) == 0 else False,
        'subscripted': True if len(subscriptions) != 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('feed.html', **params)


@app.route('/favorite')
def favorite():
    """
    Возвращает страницу с любимыми видео пользователя.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    likes = json.loads(current_user.likes)[::-1]
    liked_videos = [
    {
        'video': video,
        'author': db_sess.query(User).get(video.author) if video.author else None
    }
    for i in likes
    if (video := db_sess.query(Video).get(i)) is not None
]
    params = {
        'title': f'Любимое',
        'liked_videos': [liked_videos[i * 3:i * 3 + 3] for i in range(len(liked_videos) // 3)] +
                        [liked_videos[(len(liked_videos) // 3) * 3:]],
        'empty': True if len(liked_videos) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('favorite.html', **params)


@app.route('/people')
def people():
    """
    Возвращает страницу с пользователями, на которых подписан пользователь.
    Если пользователь не авторизован, доступ запрещен
    """
    if not current_user.is_authenticated:
        return forbidden('')
    subscriptions = json.loads(current_user.subscriptions)
    subscripted_users = [
        {
            'user': db_sess.query(User).get(i)
        }
        for i in subscriptions
    ]
    subscripted_users.sort(key=lambda x: x['user'].name + x['user'].surname + x['user'].login)
    params = {
        'title': f'Люди',
        'subscripted_users': subscripted_users,
        'empty': True if len(subscripted_users) == 0 else False,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }
    return render_template('people.html', **params)
from data.edit_profile_form import EditProfileForm  # Импортируем форму
from data.banner_form import BannerForm  # Импортируем форму
# In your main2.py (or appropriate routes file)

@app.route('/edit_profile', methods=['GET', 'POST'])
def edit_profile():
    if not current_user.is_authenticated:
        return forbidden('')
    banner_form = BannerForm()
    form = EditProfileForm(obj=current_user)
    params = {
        'title': 'Редактирование профиля',
        'authenticated': current_user.is_authenticated,
        'current_user': current_user,
        'banner_form': banner_form
    }

    if form.validate_on_submit():
        if form.login.data and form.login.data != current_user.login:
            existing_user = db_sess.query(User).filter(User.login == form.login.data).first()
            if existing_user:
                flash('Пользователь с таким логином уже существует.', 'danger')
                return render_template('edit_profile.html', form=form, **params)

        current_user.name = form.name.data
        current_user.surname = form.surname.data
        if form.password.data:
            current_user.password = pwd_context.hash(form.password.data)

        current_user.channel_description = form.channel_description.data

        # Update the birthday field
        current_user.birthday = form.birthday.data if form.birthday.data else None

        # Update theme selection
        current_user.theme_name = form.theme_name.data
        if banner_form.validate_on_submit():
            if banner_form.banner.data:
                banner_file = banner_form.banner.data
                filename = secure_filename(banner_file.filename)
                banner_path = os.path.join(app.config['UPLOAD_FOLDER'], 'banners', filename)

                # Create the banners directory if it doesn't exist
                os.makedirs(os.path.join(app.config['UPLOAD_FOLDER'], 'banners'), exist_ok=True)

                # Save the banner image
                banner_file.save(banner_path)

                # Update user's banner filename
                current_user.banner = filename
                db_sess.commit()
                flash("Баннер обновлен успешно!", "success")

            return redirect(url_for('user', user_id=current_user.id))

        if form.theme_name.data == 'custom':
            current_user.theme_primary_color = str(form.theme_primary_color.data)
            current_user.theme_secondary_color = str(form.theme_secondary_color.data)
            current_user.theme_background_color = str(form.theme_background_color.data)
        else:
            # If not custom, set theme colors to defaults based on theme name
            if form.theme_name.data == 'dark':
                current_user.theme_primary_color = '#343a40'        # Dark gray
                current_user.theme_secondary_color = '#ffffff'      # White
                current_user.theme_background_color = '#212529'     # Darker gray
            elif form.theme_name.data == 'light':
                current_user.theme_primary_color = '#ffffff'        # White
                current_user.theme_secondary_color = '#000000'      # Black
                current_user.theme_background_color = '#f8f9fa'     # Light gray
            else:  # default
                current_user.theme_primary_color = '#007bff'        # Bootstrap primary color
                current_user.theme_secondary_color = '#6c757d'      # Bootstrap secondary color
                current_user.theme_background_color = '#ffffff'     # White

        db_sess.commit()
        flash('Профиль обновлён успешно!', 'success')
        return redirect(url_for('user', user_id=current_user.id))

    return render_template('edit_profile.html', form=form, **params)
@app.route('/api/comment_like/<int:comment_id>', methods=['POST'])
def comment_like(comment_id):
    if not current_user.is_authenticated:
        return {'error': 'Unauthorized'}, 401
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return {'error': 'Comment not found'}, 404
    likes = json.loads(current_user.comment_likes)
    if comment_id in likes:
        return {'error': 'Already liked'}, 400
    likes.append(comment_id)
    current_user.comment_likes = json.dumps(likes)
    comment.likes += 1
    db_sess.commit()
    return {'success': True, 'likes': comment.likes}

@app.route('/api/comment_unlike/<int:comment_id>', methods=['POST'])
def comment_unlike(comment_id):
    if not current_user.is_authenticated:
        return {'error': 'Unauthorized'}, 401
    comment = db_sess.query(Comment).get(comment_id)
    if not comment:
        return {'error': 'Comment not found'}, 404
    likes = json.loads(current_user.comment_likes)
    if comment_id not in likes:
        return {'error': 'Not liked'}, 400
    likes.remove(comment_id)
    current_user.comment_likes = json.dumps(likes)
    comment.likes -= 1
    db_sess.commit()
    return {'success': True, 'likes': comment.likes}
@app.route('/about')
def about():
    params = {
        'title': f'Про сайт'

    }
    return render_template('about.html', **params)
from flask import request, render_template
from sqlalchemy import or_


@app.route('/найти')
@app.route('/search')
def search():
    query = request.args.get('search', '').strip().lower()
    results = []

    if query:
        search_pattern = f"%{query}%"

        # Perform case-insensitive search on title, description, and author's full name
        results = db_sess.query(Video).join(User).filter(
            db.or_(
                func.lower(Video.title).like(search_pattern),
                func.lower(Video.description).like(search_pattern),
                func.lower(func.concat(User.name, ' ', User.surname)).like(search_pattern)
            )
        ).all()

    return render_template('search_results.html', videos=results, query=query)
@app.route('/users')
def users():
    # Query all users from the database
    all_users = db_sess.query(User).all()

    # Set up template parameters
    params = {
        'title': 'Все пользователи',
        'users': all_users,
        'authenticated': current_user.is_authenticated,
        'current_user': current_user
    }

    # Render the template with the list of users
    return render_template('users.html', **params)



db_session.global_init("db/hosting.sql")

db_sess = db_session.create_session()
SqlAlchemyBase.metadata.create_all(bind=db_sess.bind)
db_sess.rollback

SqlAlchemyBase.metadata.create_all(bind=db_sess.bind)
api.add_resource(UsersResource, '/api/users/<int:user_id>')
api.add_resource(UserListResource, '/api/users')
api.add_resource(VideosResource, '/api/videos/<int:video_id>')
api.add_resource(VideoListResource, '/api/videos')
api.add_resource(LikeResource, '/api/like/<int:video_id>')
api.add_resource(NotLikeResource, '/api/not_like/<int:video_id>')
api.add_resource(FollowResource, '/api/follow/<int:user_id>')
api.add_resource(NotFollowResource, '/api/not_follow/<int:user_id>')


if __name__ == '__main__':
    from waitress import serve
    app.run(port=8080, host='0.0.0.0')

кнопки изменить и удалить видео не показываются если ты автор