hashim1 commited on
Commit
00d483a
ยท
1 Parent(s): 760792e

Add application file

Browse files
Files changed (3) hide show
  1. Dockerfile +16 -0
  2. app.py +1587 -0
  3. requirements.txt +72 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,1587 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ import time
3
+ from datetime import datetime
4
+ import json
5
+ import os
6
+ import re
7
+ import numpy as np
8
+ from flask import Flask, render_template, request, jsonify, flash, redirect, url_for, abort, Response, stream_with_context
9
+ from flask_sqlalchemy import SQLAlchemy
10
+ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
11
+ from werkzeug.security import generate_password_hash, check_password_hash
12
+ from werkzeug.utils import secure_filename
13
+ from sqlalchemy import func
14
+ from sqlalchemy.orm import relationship
15
+ from g4f.client import Client
16
+ import markdown
17
+ from PIL import Image
18
+ import imageio.v3 as iio
19
+ import numpy as np
20
+ import os
21
+ import random
22
+
23
+ app = Flask(__name__)
24
+
25
+
26
+
27
+
28
+ # Custom template filters
29
+ @app.template_filter('from_json')
30
+ def from_json(value):
31
+ try:
32
+ return json.loads(value)
33
+ except:
34
+ return []
35
+
36
+ @app.template_filter('format_datetime')
37
+ def format_datetime(value):
38
+ """Format a datetime object to a readable string"""
39
+ now = datetime.now()
40
+ diff = now - value
41
+
42
+ if diff.days == 0:
43
+ if diff.seconds < 30: # ุฃู‚ู„ ู…ู† 30 ุซุงู†ูŠุฉ
44
+ return "ุงู„ุขู†"
45
+ elif diff.seconds < 60: # ุฃู‚ู„ ู…ู† ุฏู‚ูŠู‚ุฉ
46
+ return f"ู…ู†ุฐ {diff.seconds} ุซุงู†ูŠุฉ"
47
+ elif diff.seconds < 3600: # ุฃู‚ู„ ู…ู† ุณุงุนุฉ
48
+ minutes = diff.seconds // 60
49
+ if minutes == 1:
50
+ return "ู…ู†ุฐ ุฏู‚ูŠู‚ุฉ ูˆุงุญุฏุฉ"
51
+ elif minutes == 2:
52
+ return "ู…ู†ุฐ ุฏู‚ูŠู‚ุชูŠู†"
53
+ elif minutes <= 10:
54
+ return f"ู…ู†ุฐ {minutes} ุฏู‚ุงุฆู‚"
55
+ else:
56
+ return f"ู…ู†ุฐ {minutes} ุฏู‚ูŠู‚ุฉ"
57
+ else: # ุฃู‚ู„ ู…ู† ูŠูˆู…
58
+ hours = diff.seconds // 3600
59
+ if hours == 1:
60
+ return "ู…ู†ุฐ ุณุงุนุฉ ูˆุงุญุฏุฉ"
61
+ elif hours == 2:
62
+ return "ู…ู†ุฐ ุณุงุนุชูŠู†"
63
+ elif hours <= 10:
64
+ return f"ู…ู†ุฐ {hours} ุณุงุนุงุช"
65
+ else:
66
+ return f"ู…ู†ุฐ {hours} ุณุงุนุฉ"
67
+ elif diff.days == 1:
68
+ return "ู…ู†ุฐ ูŠูˆู… ูˆุงุญุฏ"
69
+ elif diff.days == 2:
70
+ return "ู…ู†ุฐ ูŠูˆู…ูŠู†"
71
+ elif diff.days <= 10:
72
+ return f"ู…ู†ุฐ {diff.days} ุฃูŠุงู…"
73
+ elif diff.days < 30:
74
+ return f"ู…ู†ุฐ {diff.days} ูŠูˆู…ุงู‹"
75
+ else:
76
+ # ุชู†ุณูŠู‚ 12 ุณุงุนุฉ ู…ุน ุตุจุงุญุงู‹/ู…ุณุงุกู‹
77
+ return value.strftime("%I:%M %p").replace("AM", "ุตุจุงุญุงู‹").replace("PM", "ู…ุณุงุกู‹") + " " + value.strftime("%Y-%m-%d")
78
+
79
+ app.config['SECRET_KEY'] = 'your-secret-key'
80
+ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db?check_same_thread=False'
81
+
82
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
83
+ app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
84
+ 'charset': 'utf8mb4' # ุชุนูŠูŠู† ุงู„ุชุฑู…ูŠุฒ UTF-8 ู„ุฌู…ูŠุน ุงู„ุฌุฏุงูˆู„
85
+ }
86
+ app.config['UPLOAD_FOLDER2'] = 'static/videos/video'
87
+ app.config['THUMBNAIL_FOLDER'] = 'static/videos/thumbnails'
88
+ from sqlalchemy.pool import NullPool
89
+
90
+ app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {'poolclass': NullPool}
91
+ from sqlalchemy.orm import scoped_session, sessionmaker
92
+
93
+ # ุฅู†ุดุงุก ุฌู„ุณุฉ ุฏูŠู†ุงู…ูŠูƒูŠุฉ
94
+
95
+
96
+ db = SQLAlchemy(app)
97
+ login_manager = LoginManager(app)
98
+ login_manager.login_view = 'login'
99
+
100
+ class User(UserMixin, db.Model):
101
+ id = db.Column(db.Integer, primary_key=True)
102
+ username = db.Column(db.String(80), unique=True, nullable=False)
103
+ email = db.Column(db.String(120), unique=True, nullable=False)
104
+ password = db.Column(db.String(120), nullable=False)
105
+ profession = db.Column(db.String(120), nullable=False)
106
+ skills = db.Column(db.String(500), nullable=False)
107
+ security_question = db.Column(db.String(200), nullable=False)
108
+ security_answer = db.Column(db.String(200), nullable=False)
109
+ profile_photo = db.Column(db.String(200), nullable=True, default='uploads/default-avatar.jpg')
110
+
111
+
112
+ # ุฅุถุงูุฉ ู†ู…ูˆุฐุฌ Post
113
+ class Comment(db.Model):
114
+ id = db.Column(db.Integer, primary_key=True)
115
+ content = db.Column(db.Text, nullable=False)
116
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
117
+ post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
118
+ parent_id = db.Column(db.Integer, db.ForeignKey('comment.id'), nullable=True) # Parent comment ID for nested replies
119
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
120
+
121
+ # Relationship for nested replies
122
+ replies = relationship('Comment', backref=db.backref('parent', remote_side=[id]), lazy='dynamic')
123
+
124
+ class Post(db.Model):
125
+ id = db.Column(db.Integer, primary_key=True)
126
+ title = db.Column(db.String(120), nullable=True) # ุนู†ูˆุงู† ุงุฎุชูŠุงุฑูŠ
127
+ content = db.Column(db.Text, nullable=True) # ู…ุญุชูˆู‰ ุงู„ู†ุต
128
+ image_url = db.Column(db.String(120), nullable=True) # ุฑุงุจุท ุงู„ุตูˆุฑุฉ
129
+ video_url = db.Column(db.String(120), nullable=True) # ุฑุงุจุท ุงู„ููŠุฏูŠูˆ
130
+ background_color = db.Column(db.String(20), nullable=True) # ู„ูˆู† ุฎู„ููŠุฉ ุงู„ู†ุต
131
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
132
+ user_email = db.Column(db.String(120), nullable=False) # ุฅูŠู…ูŠู„ ุงู„ู…ุณุชุฎุฏู…
133
+ user_profession = db.Column(db.String(120), nullable=False) # ุชุฎุตุต ุงู„ู…ุณุชุฎุฏู…
134
+ created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
135
+
136
+ def __repr__(self):
137
+ return f'<Post {self.title}>'
138
+
139
+ @login_manager.user_loader
140
+ def load_user(user_id):
141
+ return User.query.get(int(user_id))
142
+
143
+ # ู†ู…ูˆุฐุฌ ุงู„ุฑุณุงู„ุฉ
144
+ class Message(db.Model):
145
+ id = db.Column(db.Integer, primary_key=True)
146
+ sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
147
+ receiver_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
148
+ content = db.Column(db.Text) # ู†ุต ุงู„ุฑุณุงู„ุฉ (ุงุฎุชูŠุงุฑูŠ)
149
+ file_url = db.Column(db.String(255)) # ุฑุงุจุท ุงู„ู…ู„ู (ุตูˆุฑุฉ/ููŠุฏูŠูˆ)
150
+ file_type = db.Column(db.String(50)) # ู†ูˆุน ุงู„ู…ู„ู: 'image', 'video', 'audio', ุฅู„ุฎ
151
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
152
+ watched_by_receiver = db.Column(db.Boolean, default=False)
153
+ watched_by_sender = db.Column(db.Boolean, default=False)
154
+ last_updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
155
+
156
+
157
+
158
+ # ุชุนุฑูŠู ู†ู…ูˆุฐุฌ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
159
+ class Article(db.Model):
160
+ id = db.Column(db.Integer, primary_key=True)
161
+ title = db.Column(db.String(200), nullable=False)
162
+ content = db.Column(db.Text, nullable=False)
163
+ user = db.Column(db.String(100), nullable=False)
164
+ id_user = db.Column(db.String(100), nullable=False)
165
+ photo_user = db.Column(db.String(100), nullable=False)
166
+ profession_user = db.Column(db.String(100), nullable=False)
167
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
168
+
169
+
170
+ class Video(db.Model):
171
+ id = db.Column(db.Integer, primary_key=True)
172
+ video_path = db.Column(db.String(200), nullable=False)
173
+ thumbnail_path = db.Column(db.String(200), nullable=True)
174
+ uploader_name = db.Column(db.String(80), nullable=False)
175
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
176
+ uploader_image = db.Column(db.String(200), nullable=True)
177
+ upload_time = db.Column(db.DateTime, default=datetime.utcnow)
178
+ title = db.Column(db.String(200), nullable=False) # ุนู†ูˆุงู† ุงู„ููŠุฏูŠูˆ
179
+ category = db.Column(db.String(100), nullable=False) # ูุฆุฉ ุงู„ููŠุฏูŠูˆ
180
+
181
+
182
+ class Playlist(db.Model):
183
+ id = db.Column(db.Integer, primary_key=True)
184
+ name = db.Column(db.String(200), nullable=False)
185
+ is_public = db.Column(db.Boolean, default=True) # True = ุนุงู…ุฉ, False = ุฎุงุตุฉ
186
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
187
+ created_at = db.Column(db.DateTime, default=datetime.utcnow)
188
+
189
+
190
+
191
+ class PlaylistVideo(db.Model):
192
+ id = db.Column(db.Integer, primary_key=True)
193
+ playlist_id = db.Column(db.Integer, db.ForeignKey('playlist.id'), nullable=False)
194
+ video_id = db.Column(db.Integer, db.ForeignKey('video.id'), nullable=False)
195
+ added_at = db.Column(db.DateTime, default=datetime.utcnow)
196
+
197
+ @app.route('/post/<int:post_id>')
198
+ def view_post(post_id):
199
+ # ุฌู„ุจ ุงู„ู…ู†ุดูˆุฑ ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ู€ ID
200
+ post = Post.query.get_or_404(post_id)
201
+
202
+ # ุฌู„ุจ ุจูŠุงู†ุงุช ุงู„ู…ุณุชุฎุฏู… ุจู†ุงุกู‹ ุนู„ู‰ ID ุงู„ู…ุณุชุฎุฏู… ุงู„ู…ุฑุชุจุท ุจุงู„ู…ู†ุดูˆุฑ
203
+ user = User.query.get_or_404(post.user_id)
204
+
205
+ # ุฌู„ุจ ุฌู…ูŠุน ุงู„ู…ู†ุดูˆุฑุงุช ู„ุนุฑุถู‡ุง ููŠ dashboard.html ุจุงุณุชุซู†ุงุก ุงู„ู…ู†ุดูˆุฑ ุงู„ุฐูŠ ูŠุชู… ุนุฑุถ ุชูุงุตูŠู„ู‡
206
+ posts = Post.query.filter(
207
+ Post.user_profession == current_user.profession,
208
+ Post.user_email != current_user.email,
209
+ Post.id != post_id # ุงุณุชุจุนุงุฏ ุงู„ู…ู†ุดูˆุฑ ุงู„ุฐูŠ ูŠุชู… ุนุฑุถู‡ ุญุงู„ูŠุง
210
+ ).order_by(Post.id.desc()).all()
211
+
212
+ # ุจู†ุงุก ู‚ุงู…ูˆุณ ูŠุญุชูˆูŠ ุนู„ู‰ ุตูˆุฑ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู„ูƒู„ ู…ุณุชุฎุฏู…
213
+ post_authors = {}
214
+ for p in posts:
215
+ if p.user_email not in post_authors:
216
+ author = User.query.filter_by(email=p.user_email).first()
217
+ post_authors[p.user_email] = author.profile_photo if author else 'uploads/default-avatar.jpg'
218
+
219
+ # ุชู…ุฑูŠุฑ ุจูŠุงู†ุงุช ุงู„ู…ู†ุดูˆุฑ ุฅู„ู‰ dashboard.html ู…ุน ุฅุดุงุฑุฉ ู„ูุชุญ modal
220
+ return render_template('dashboard.html', posts=posts, post=post, user=user, show_post_modal=True, post_authors=post_authors)
221
+
222
+ # ู„ุณู‡ ุชุงุจุน ุงู„ููƒุฑุฉ
223
+ # @app.route('/post/<int:post_id>')
224
+ # def view_post(post_id):
225
+ # post = Post.query.get_or_404(post_id)
226
+ # # ุฌู„ุจ ุจูŠุงู†ุงุช ุงู„ู…ุณุชุฎุฏู… ุงู„ู…ุฑุชุจุท ุจุงู„ู…ู†ุดูˆุฑ
227
+ # user = User.query.get_or_404(post.user_id)
228
+
229
+ # # ุนู†ุฏ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุฑุงุจุท ุงู„ู…ู†ุดูˆุฑุŒ ู†ู…ุฑุฑ ุงู„ู…ู†ุดูˆุฑ ุฅู„ู‰ ุตูุญุฉ dashboard.html
230
+ # return render_template('dashboard.html', post=post, user=user)
231
+
232
+ @app.route('/share/<int:post_id>', methods=['POST'])
233
+ def share_post(post_id):
234
+ post = Post.query.get_or_404(post_id)
235
+ db.session.commit()
236
+ return jsonify({"success": True})
237
+
238
+
239
+
240
+ @app.route('/')
241
+ def home():
242
+ if current_user.is_authenticated:
243
+ return redirect(url_for('dashboard'))
244
+ return redirect(url_for('login'))
245
+
246
+ @app.route('/register', methods=['GET', 'POST'])
247
+ def register():
248
+ if request.method == 'POST':
249
+ username = request.form.get('username')
250
+ email = f"{username}@moltka.eg"
251
+ password = request.form.get('password')
252
+ confirm_password = request.form.get('confirm_password')
253
+ profession = request.form.get('profession')
254
+ skills = request.form.get('skills')
255
+ security_question = request.form.get('security_question')
256
+ security_answer = request.form.get('security_answer')
257
+
258
+ # ุงู„ุชุฃูƒุฏ ู…ู† ุชุทุงุจู‚ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ู…ุน ุชุฃูƒูŠุฏ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ
259
+ if password != confirm_password:
260
+ flash('ูƒู„ู…ุงุช ุงู„ู…ุฑูˆุฑ ุบูŠุฑ ู…ุชุทุงุจู‚ุฉ!', 'error')
261
+ return redirect(url_for('register'))
262
+
263
+ # ุงู„ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
264
+ user = User.query.filter_by(email=email).first()
265
+ if user:
266
+ flash('ุงุณู… ุงู„ู…ุณุชุฎุฏู… ู…ูˆุฌูˆุฏ ุจุงู„ูุนู„!', 'error')
267
+ return redirect(url_for('register'))
268
+
269
+ # ู„ุง ุญุงุฌุฉ ู„ุชุดููŠุฑ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ู‡ู†ุงุŒ ุณูŠุชู… ุชุฎุฒูŠู†ู‡ุง ูƒู†ุต ุนุงุฏูŠ
270
+ new_user = User(
271
+ username=username,
272
+ email=email,
273
+ password=password, # ุชุฎุฒูŠู† ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุจุฏูˆู† ุชุดููŠุฑ
274
+ profession=profession,
275
+ skills=skills,
276
+ security_question=security_question,
277
+ security_answer=security_answer
278
+ )
279
+
280
+ db.session.add(new_user)
281
+ db.session.commit()
282
+
283
+ flash('ุชู… ุฅู†ุดุงุก ุงู„ุญุณุงุจ ุจู†ุฌุงุญ!', 'success')
284
+ return redirect(url_for('login'))
285
+
286
+ return render_template('register.html')
287
+
288
+ @app.route('/login', methods=['GET', 'POST'])
289
+ def login():
290
+ if request.method == 'POST':
291
+ username = request.form.get('username')
292
+ email = f"{username}@moltka.eg"
293
+ password = request.form.get('password')
294
+
295
+ # ุงู„ุจุญุซ ุนู† ุงู„ู…ุณุชุฎุฏู… ุจุงุณุชุฎุฏุงู… ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ
296
+ user = User.query.filter_by(email=email).first()
297
+
298
+ # ุงู„ุชุญู‚ู‚ ู…ู† ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ู…ุฏุฎู„ุฉ ู…ู‚ุงุจู„ ุงู„ู…ุฎุฒู†ุฉ ูƒู†ุต ุนุงุฏูŠ
299
+ if user and user.password == password: # ู…ู‚ุงุฑู†ุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ู…ุฏุฎู„ุฉ ู…ุน ุงู„ู…ุฎุฒู†ุฉ
300
+ login_user(user)
301
+ return redirect(url_for('dashboard'))
302
+
303
+ flash('ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุฃูˆ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุบูŠุฑ ุตุญูŠุญุฉ!', 'error')
304
+ return render_template('login.html')
305
+
306
+ @app.route('/dashboard')
307
+ @login_required
308
+ def dashboard():
309
+ # ุญุณุงุจ ุงู„ู„ูˆู† ุจู†ุงุกู‹ ุนู„ู‰ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุจุงุณุชุฎุฏุงู… hash
310
+ user_hash = str(uuid.uuid5(uuid.NAMESPACE_DNS, current_user.username))[:6] # ุฃูˆู„ 6 ุฃุญุฑู ู…ู† ุงู„ู€ UUID
311
+
312
+ # ุฌู„ุจ ุงู„ู…ู†ุดูˆุฑุงุช ุงู„ุชูŠ ุชุทุงุจู‚ ุชุฎุตุต ุงู„ู…ุณุชุฎุฏู… ูˆู„ู… ูŠู‚ู… ุจู†ุดุฑู‡ุง
313
+ posts = Post.query.filter(
314
+ Post.user_profession == current_user.profession, # ู†ูุณ ุงู„ุชุฎุตุต
315
+ Post.user_email != current_user.email # ู„ูŠุณุช ู…ู† ู…ู†ุดูˆุฑุงุช ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
316
+ ).order_by(Post.id.desc()).all()
317
+
318
+ # ุฌู„ุจ ุตูˆุฑ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู„ูƒู„ ู…ุณุชุฎุฏู…
319
+ post_authors = {}
320
+ for post in posts:
321
+ if post.user_email not in post_authors:
322
+ author = User.query.filter_by(email=post.user_email).first()
323
+ post_authors[post.user_email] = author.profile_photo if author else 'uploads/default-avatar.jpg'
324
+
325
+ # ุญุณุงุจ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ ุจุงุณุชุฎุฏุงู… watched_by_receiver
326
+ unread_count = Message.query.filter_by(receiver_id=current_user.id, watched_by_receiver=False).count()
327
+
328
+ return render_template('dashboard.html', user_hash=user_hash, posts=posts, post_authors=post_authors, unread_count=unread_count)
329
+ @app.route('/logout')
330
+ @login_required
331
+ def logout():
332
+ logout_user()
333
+ flash('ุชู… ุชุณุฌูŠู„ ุงู„ุฎุฑูˆุฌ ุจู†ุฌุงุญ', 'success')
334
+ return redirect(url_for('login'))
335
+
336
+ @app.route('/validate-username')
337
+ def validate_username():
338
+ username = request.args.get('username')
339
+ email = f"{username}@moltka.eg"
340
+ user = User.query.filter_by(email=email).first()
341
+ return jsonify({'exists': user is not None})
342
+
343
+ @app.route('/forgot_password', methods=['GET', 'POST'])
344
+ def forgot_password():
345
+ if request.method == 'POST':
346
+ email = request.form.get('email')
347
+ user = User.query.filter_by(email=email).first()
348
+
349
+ if user:
350
+ return render_template('reset_password.html', email=email)
351
+ else:
352
+ flash('ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุบูŠุฑ ู…ูˆุฌูˆุฏ!', 'error')
353
+
354
+ return render_template('forgot_password.html')
355
+
356
+ @app.route('/reset_password', methods=['POST'])
357
+ def reset_password():
358
+ email = request.form.get('email')
359
+ security_answer = request.form.get('security_answer')
360
+ new_password = request.form.get('new_password')
361
+ confirm_password = request.form.get('confirm_password')
362
+
363
+ user = User.query.filter_by(email=email).first()
364
+
365
+ if user and user.security_answer == security_answer:
366
+ if new_password == confirm_password:
367
+ hashed_password = generate_password_hash(new_password)
368
+ user.password = hashed_password
369
+ db.session.commit()
370
+ flash('ุชู… ุชุบูŠูŠุฑ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุจู†ุฌุงุญ!', 'success')
371
+ return redirect(url_for('login'))
372
+ else:
373
+ flash('ูƒู„ู…ุงุช ุงู„ู…ุฑูˆุฑ ุบูŠุฑ ู…ุชุทุงุจู‚ุฉ!', 'error')
374
+ else:
375
+ flash('ุงู„ุฅุฌุงุจุฉ ุนู„ู‰ ุณุคุงู„ ุงู„ุฃู…ุงู† ุบูŠุฑ ุตุญูŠุญุฉ!', 'error')
376
+
377
+ return redirect(url_for('forgot_password'))
378
+
379
+
380
+ @app.route('/validate_security_answer', methods=['POST'])
381
+ def validate_security_answer():
382
+ data = request.get_json()
383
+ email = data.get('email')
384
+ security_answer = data.get('security_answer')
385
+
386
+ user = User.query.filter_by(email=email).first()
387
+ if user and user.security_answer == security_answer:
388
+ return jsonify({'valid': True})
389
+ else:
390
+ return jsonify({'valid': False})
391
+
392
+ @app.route('/profile')
393
+ @login_required
394
+ def profile():
395
+ # ุฌู„ุจ ู…ู†ุดูˆุฑุงุช ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ูู‚ุท
396
+ posts = Post.query.filter_by(
397
+ user_email=current_user.email
398
+ ).order_by(Post.id.desc()).all()
399
+
400
+ return render_template('profile.html', posts=posts)
401
+
402
+
403
+
404
+ @app.route('/profile/<user_email>')
405
+ def public_profile(user_email):
406
+ user = User.query.filter_by(email=user_email).first()
407
+ if not user:
408
+ abort(404) # ุงู„ู…ุณุชุฎุฏู… ุบูŠุฑ ู…ูˆุฌูˆุฏ
409
+ posts = Post.query.filter_by(user_email=user_email).all()
410
+ return render_template('public_profile.html', user=user, posts=posts)
411
+
412
+
413
+ @app.route('/edit_post_content', methods=['POST'])
414
+ @login_required
415
+ def edit_post_content():
416
+ post_id = request.form.get('post_id')
417
+ new_content = request.form.get('new_content')
418
+
419
+ post = Post.query.get(post_id)
420
+ if post and post.user_email == current_user.email:
421
+ post.content = new_content
422
+ db.session.commit()
423
+ return jsonify({'success': True})
424
+ return jsonify({'success': False})
425
+
426
+ @app.route('/delete_post', methods=['POST'])
427
+ @login_required
428
+ def delete_post():
429
+ post_id = request.form.get('post_id')
430
+ post = Post.query.get(post_id)
431
+
432
+ if post and post.user_email == current_user.email:
433
+ db.session.delete(post)
434
+ db.session.commit()
435
+ return jsonify({'success': True})
436
+ return jsonify({'success': False})
437
+
438
+ @app.route('/update_profile', methods=['POST'])
439
+ @login_required
440
+ def update_profile():
441
+ username = request.form.get('username')
442
+ profession = request.form.get('profession')
443
+ skills = request.form.get('skills')
444
+
445
+ if not username or not profession:
446
+ flash('ุฌู…ูŠุน ุงู„ุญู‚ูˆู„ ู…ุทู„ูˆุจุฉ!', 'error')
447
+ return redirect(url_for('profile'))
448
+
449
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุบูŠุฑ ู…ุณุชุฎุฏู… ู…ู† ู‚ุจู„ ู…ุณุชุฎุฏู… ุขุฎุฑ
450
+ if username != current_user.username:
451
+ existing_user = User.query.filter_by(username=username).first()
452
+ if existing_user:
453
+ flash('ุงุณู… ุงู„ู…ุณุชุฎุฏู… ู…ุณุชุฎุฏู… ุจุงู„ูุนู„!', 'error')
454
+ return redirect(url_for('profile'))
455
+
456
+ try:
457
+ # ุชุญุฏูŠุซ ุจูŠุงู†ุงุช ุงู„ู…ุณุชุฎุฏู…
458
+ current_user.username = username
459
+ current_user.email = f"{username}@moltka.eg"
460
+ current_user.profession = profession
461
+ current_user.skills = skills
462
+
463
+ # ู…ุนุงู„ุฌุฉ ุงู„ุตูˆุฑุฉ ุงู„ุดุฎุตูŠุฉ
464
+ avatar = request.files.get('avatar')
465
+ remove_photo = request.form.get('remove_photo')
466
+
467
+ if remove_photo:
468
+ # ุฅุนุงุฏุฉ ุชุนูŠูŠู† ุงู„ุตูˆุฑุฉ ุงู„ุงูุชุฑุงุถูŠุฉ
469
+ if current_user.profile_photo != 'uploads/default-avatar.jpg':
470
+ # ุญุฐู ุงู„ุตูˆุฑุฉ ุงู„ู‚ุฏูŠู…ุฉ ุฅุฐุง ูƒุงู†ุช ู…ูˆุฌูˆุฏุฉ
471
+ old_photo_path = os.path.join('static', current_user.profile_photo)
472
+ if os.path.exists(old_photo_path) and 'default-avatar' not in old_photo_path:
473
+ os.remove(old_photo_path)
474
+ current_user.profile_photo = 'uploads/default-avatar.jpg'
475
+ elif avatar and avatar.filename:
476
+ # ุญุฐู ุงู„ุตูˆุฑุฉ ุงู„ู‚ุฏูŠู…ุฉ ุฅุฐุง ูƒุงู†ุช ู…ูˆุฌูˆุฏุฉ
477
+ if current_user.profile_photo != 'uploads/default-avatar.jpg':
478
+ old_photo_path = os.path.join('static', current_user.profile_photo)
479
+ if os.path.exists(old_photo_path) and 'default-avatar' not in old_photo_path:
480
+ os.remove(old_photo_path)
481
+
482
+ # ุญูุธ ุงู„ุตูˆุฑุฉ ุงู„ุฌุฏูŠุฏุฉ
483
+ filename = f"{int(time.time())}_{secure_filename(avatar.filename)}"
484
+ avatar.save(os.path.join('static/uploads', filename))
485
+ current_user.profile_photo = 'uploads/' + filename
486
+
487
+ # ุชุญุฏูŠุซ ุงู„ู…ู†ุดูˆุฑุงุช ุงู„ู…ุฑุชุจุทุฉ ุจุงู„ู…ุณุชุฎุฏู…
488
+ posts = Post.query.filter_by(user_email=f"{current_user.username}@moltka.eg").all()
489
+ for post in posts:
490
+ post.user_email = f"{username}@moltka.eg"
491
+ post.user_profession = profession
492
+
493
+ db.session.commit()
494
+ flash('ุชู… ุชุญุฏูŠุซ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ุจู†ุฌุงุญ!', 'success')
495
+ except Exception as e:
496
+ db.session.rollback()
497
+ flash('ุญุฏุซ ุฎุทุฃ ุฃุซู†ุงุก ุชุญุฏูŠุซ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ!', 'error')
498
+ print(f"Error updating profile: {str(e)}")
499
+
500
+ return redirect(url_for('profile'))
501
+
502
+
503
+
504
+
505
+ @app.route('/add_comment', methods=['POST'])
506
+ @login_required
507
+ def add_comment():
508
+ post_id = request.form.get('post_id')
509
+ content = request.form.get('content')
510
+ parent_id = request.form.get('parent_id') # Optional parent comment ID for replies
511
+
512
+ if not content or not post_id:
513
+ return jsonify({'success': False, 'message': 'Content and post ID are required.'})
514
+
515
+ try:
516
+ # Create new comment
517
+ comment = Comment(
518
+ content=content,
519
+ user_id=current_user.id,
520
+ post_id=post_id,
521
+ parent_id=parent_id if parent_id else None, # Associate reply with parent comment if provided
522
+ created_at=datetime.now()
523
+ )
524
+ db.session.add(comment)
525
+ db.session.commit()
526
+ return jsonify({'success': True})
527
+ except Exception as e:
528
+ print(f"Error adding comment: {str(e)}")
529
+ return jsonify({'success': False, 'message': 'An error occurred while adding the comment.'})
530
+
531
+
532
+ def serialize_comment(comment):
533
+ """Serialize comment and its nested replies."""
534
+ return {
535
+ 'id': comment.id,
536
+ 'content': comment.content,
537
+ 'username': User.query.get(comment.user_id).username if comment.user_id else 'Unknown User',
538
+ 'created_at': format_datetime(comment.created_at),
539
+ 'profile_photo': User.query.get(comment.user_id).profile_photo if comment.user_id else 'uploads/default-avatar.jpg',
540
+ 'replies': [serialize_comment(reply) for reply in comment.replies.order_by(Comment.created_at.asc())]
541
+ }
542
+
543
+ @app.route('/get_comments/<int:post_id>')
544
+ @login_required
545
+ def get_comments(post_id):
546
+ comments = Comment.query.filter_by(post_id=post_id, parent_id=None).order_by(Comment.created_at.desc()).all()
547
+ serialized_comments = [serialize_comment(comment) for comment in comments]
548
+ return jsonify(serialized_comments)
549
+
550
+
551
+ @app.route('/create_post', methods=['GET', 'POST'])
552
+ @login_required
553
+ def create_post():
554
+ if request.method == 'GET':
555
+ return render_template('create_post.html')
556
+
557
+ content = request.form.get('content')
558
+ media = request.files.get('media')
559
+ background_color = request.form.get('background_color')
560
+
561
+ # ู…ุนุงู„ุฌุฉ ุงู„ูˆุณุงุฆุท ุงู„ู…ุฑูู‚ุฉ
562
+ image_url = None
563
+ video_url = None
564
+ if media and media.filename:
565
+ filename = f"{int(time.time())}_{secure_filename(media.filename)}"
566
+ if media.filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')):
567
+ image_url = 'uploads/' + filename
568
+ media.save(os.path.join('static/uploads', filename))
569
+ elif media.filename.lower().endswith(('.mp4', '.mov', '.avi')):
570
+ video_url = 'uploads/' + filename
571
+ media.save(os.path.join('static/uploads', filename))
572
+
573
+ # ุฅู†ุดุงุก ุงู„ู…ู†ุดูˆุฑ ุจุงุณุชุฎุฏุงู… ุงู„ุชูˆู‚ูŠุช ุงู„ู…ุญู„ูŠ
574
+ post = Post(
575
+ content=content,
576
+ image_url=image_url,
577
+ video_url=video_url,
578
+ background_color=background_color,
579
+ user_id=current_user.id,
580
+ user_email=current_user.email, # ุฅุถุงูุฉ ุฅูŠู…ูŠู„ ุงู„ู…ุณุชุฎุฏู… ุชู„ู‚ุงุฆูŠุงู‹
581
+ user_profession=current_user.profession, # ุฅุถุงูุฉ ุชุฎุตุต ุงู„ู…ุณุชุฎุฏู… ุชู„ู‚ุงุฆูŠุงู‹
582
+ created_at=datetime.now() # ุงุณุชุฎุฏุงู… ุงู„ุชูˆู‚ูŠุช ุงู„ู…ุญู„ูŠ
583
+ )
584
+ db.session.add(post)
585
+ db.session.commit()
586
+
587
+ # ุฅุฑุณุงู„ ุฑุณุงู„ุฉ ู†ุฌุงุญ ุนุจุฑ JavaScript
588
+ return """
589
+ <script>
590
+ window.parent.postMessage('success', '*');
591
+ window.parent.location.reload();
592
+ </script>
593
+ """
594
+
595
+ # ุชุญู…ูŠู„ ุงู„ู…ุณุชุฎุฏู…
596
+ @login_manager.user_loader
597
+ def load_user(user_id):
598
+ return User.query.get(int(user_id))
599
+
600
+ active_users = set() # ู‚ุงุฆู…ุฉ ุจุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู†ุดุทูŠู† ููŠ ุงู„ู…ุญุงุฏุซุฉ
601
+
602
+ print(active_users)
603
+ # ุตูุญุฉ ุงู„ุฑุณุงุฆู„
604
+ @app.route('/messages/<user_email>')
605
+ @login_required
606
+ def messages(user_email):
607
+ user = User.query.filter_by(email=user_email).first()
608
+ if not user:
609
+ abort(404)
610
+
611
+ # ุฅุถุงูุฉ ุงู„ู…ุณุชุฎุฏู… ุฅู„ู‰ ู‚ุงุฆู…ุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู†ุดุทูŠู†
612
+ active_users.add(current_user.id)
613
+ active_users.add(user.id)
614
+
615
+ # ุชุญุฏูŠุซ ุงู„ุฑุณุงุฆู„ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ" ู…ู† ู‚ุจู„ ุงู„ู…ุณุชู‚ุจู„ ุนู†ุฏ ูุชุญ ุงู„ุตูุญุฉ
616
+ Message.query.filter_by(receiver_id=current_user.id, sender_id=user.id, watched_by_receiver=False).update({'watched_by_receiver': True})
617
+ db.session.commit()
618
+
619
+ return render_template('messages.html', user=user, current_user=current_user)
620
+
621
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ุฑุณุงุฆู„
622
+ @app.route('/get_messages/<int:user_id>', methods=['GET'])
623
+ @login_required
624
+ def get_messages(user_id):
625
+ # ุชุญุฏูŠุซ ุญุงู„ุฉ ุงู„ุฑุณุงุฆู„ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ" ู…ู† ู‚ุจู„ ุงู„ู…ุณุชู‚ุจู„ ุนู†ุฏ ูุชุญ ุงู„ู…ุญุงุฏุซุฉ
626
+ Message.query.filter_by(receiver_id=current_user.id, sender_id=user_id, watched_by_receiver=False).update({'watched_by_receiver': True})
627
+ db.session.commit()
628
+
629
+ # ุชุญุฏูŠุซ ุญุงู„ุฉ ุงู„ุฑุณุงุฆู„ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ" ู…ู† ู‚ุจู„ ุงู„ู…ุฑุณู„ ุนู†ุฏ ูุชุญ ุงู„ู…ุญุงุฏุซุฉ
630
+ Message.query.filter_by(sender_id=current_user.id, receiver_id=user_id, watched_by_sender=False).update({'watched_by_sender': True})
631
+ db.session.commit()
632
+
633
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ุฑุณุงุฆู„ ุจุชุฑุชูŠุจ ุชุตุงุนุฏูŠ
634
+ messages = Message.query.filter(
635
+ (Message.sender_id == current_user.id) & (Message.receiver_id == user_id) |
636
+ (Message.sender_id == user_id) & (Message.receiver_id == current_user.id)
637
+ ).order_by(Message.created_at.asc()).all()
638
+
639
+ messages_data = [
640
+ {
641
+ 'id': message.id,
642
+ 'sender_id': message.sender_id,
643
+ 'receiver_id': message.receiver_id,
644
+ 'content': message.content,
645
+ 'file_url': message.file_url,
646
+ 'file_type': message.file_type,
647
+ 'created_at': message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
648
+ 'watched_by_receiver': message.watched_by_receiver, # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุณุชู‚ุจู„
649
+ 'watched_by_sender': message.watched_by_sender # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุฑุณู„
650
+ } for message in messages
651
+ ]
652
+ return jsonify(messages_data)
653
+
654
+ # ุฅุฑุณุงู„ ุฑุณุงู„ุฉ
655
+
656
+
657
+ # ุฅุนุฏุงุฏุงุช ุงู„ุฑูุน
658
+ app.config['UPLOAD_FOLDER'] = 'static/uploads' # ู…ุฌู„ุฏ ุงู„ุฑูุน
659
+ app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif', 'mp4', 'mov', 'avi'} # ุงู„ุงู…ุชุฏุงุฏุงุช ุงู„ู…ุณู…ูˆุญุฉ
660
+
661
+ # ุฏุงู„ุฉ ู„ู„ุชุญู‚ู‚ ู…ู† ุงู…ุชุฏุงุฏ ุงู„ู…ู„ู
662
+ def allowed_file(filename):
663
+ return '.' in filename and \
664
+ filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
665
+
666
+ # ุฏุงู„ุฉ ู„ุชุญู…ูŠู„ ุงู„ู…ู„ู
667
+ # ุฏุงู„ุฉ ู„ุชุญู…ูŠู„ ุงู„ู…ู„ู
668
+ def upload_file(file):
669
+ if file and allowed_file(file.filename):
670
+ # ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ู…ู„ู ูŠุญุชูˆูŠ ุนู„ู‰ ุงุณู… ุตุงู„ุญ
671
+ filename = secure_filename(file.filename)
672
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
673
+ file.save(filepath) # ุญูุธ ุงู„ู…ู„ู ููŠ ุงู„ู…ุฌู„ุฏ ุงู„ู…ุญุฏุฏ
674
+ # ุชุนุฏูŠู„ ุงู„ู…ุณุงุฑ ู„ูŠูƒูˆู† ู…ุชูˆุงูู‚ู‹ุง ู…ุน ุงู„ูˆูŠุจ
675
+ return filepath.replace("\\", "/") # ุงุณุชุจุฏุงู„ "\" ุจู€ "/"
676
+ return None # ุฅุฐุง ูƒุงู† ุงู„ู…ู„ู ุบูŠุฑ ุตุงู„ุญ ุฃูˆ ู„ู… ูŠุชู… ุฑูุนู‡
677
+
678
+ # ู†ู‚ุทุฉ ุงู„ู†ู‡ุงูŠุฉ ู„ุฅุฑุณุงู„ ุงู„ุฑุณุงู„ุฉ
679
+ @app.route('/send_message', methods=['POST'])
680
+ @login_required
681
+ def send_message():
682
+ data = request.form
683
+ receiver_id = data.get('receiver_id')
684
+ content = data.get('content')
685
+ file = request.files.get('file')
686
+
687
+ if not receiver_id or (not content and not file):
688
+ return jsonify({'success': False, 'error': 'Missing receiver_id or content/file'}), 400
689
+
690
+ try:
691
+ # ุฅู†ุดุงุก ุฑุณุงู„ุฉ ุฌุฏูŠุฏุฉ
692
+ new_message = Message(
693
+ sender_id=current_user.id,
694
+ receiver_id=receiver_id,
695
+ content=content,
696
+ file_url=upload_file(file) if file else None, # ุงุณุชุฎุฏุงู… ุฏุงู„ุฉ upload_file ู„ุชุญู…ูŠู„ ุงู„ู…ู„ู
697
+ file_type=file.content_type if file else None,
698
+ watched_by_receiver=False,
699
+ watched_by_sender=True
700
+ )
701
+ db.session.add(new_message)
702
+ db.session.commit()
703
+
704
+ # ุฅุฑุฌุงุน ู…ุนุฑู ุงู„ุฑุณุงู„ุฉ ููŠ ุงู„ุงุณุชุฌุงุจุฉ
705
+ return jsonify({
706
+ 'success': True,
707
+ 'message_id': new_message.id # ุชุฃูƒุฏ ู…ู† ุฅุฑุฌุงุน ู…ุนุฑู ุงู„ุฑุณุงู„ุฉ
708
+ })
709
+ except Exception as e:
710
+ db.session.rollback()
711
+ return jsonify({'success': False, 'error': str(e)}), 500
712
+ # SSE endpoint
713
+ @app.route('/stream_messages/<int:user_id>')
714
+ @login_required
715
+ def stream_messages(user_id):
716
+ def event_stream():
717
+ last_message_id = None # ู„ุชุชุจุน ุขุฎุฑ ุฑุณุงู„ุฉ ุชู… ุฅุฑุณุงู„ู‡ุง
718
+
719
+ while True:
720
+ try:
721
+ # ุชุญู‚ู‚ ู…ู† ูˆุฌูˆุฏ ุฑุณุงุฆู„ ุฌุฏูŠุฏุฉ
722
+ query = Message.query.filter(
723
+ ((Message.sender_id == current_user.id) & (Message.receiver_id == user_id)) |
724
+ ((Message.sender_id == user_id) & (Message.receiver_id == current_user.id))
725
+ ).order_by(Message.created_at.desc())
726
+
727
+ if last_message_id:
728
+ query = query.filter(Message.id > last_message_id) # ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุฑุณุงุฆู„ ุงู„ุฌุฏูŠุฏุฉ ูู‚ุท
729
+
730
+ messages = query.limit(1).all()
731
+
732
+ if messages:
733
+ message = messages[0]
734
+ last_message_id = message.id # ุชุญุฏูŠุซ ุขุฎุฑ ุฑุณุงู„ุฉ ุชู… ุฅุฑุณุงู„ู‡ุง
735
+
736
+ # ุฅุฑุณุงู„ ุจูŠุงู†ุงุช ุงู„ุฑุณุงู„ุฉ
737
+ yield f"data: {json.dumps({
738
+ 'message_id': message.id,
739
+ 'sender_id': message.sender_id,
740
+ 'receiver_id': message.receiver_id,
741
+ 'content': message.content,
742
+ 'file_url': message.file_url,
743
+ 'file_type': message.file_type,
744
+ 'created_at': message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
745
+ 'watched_by_receiver': message.watched_by_receiver, # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุณุชู‚ุจู„
746
+ 'watched_by_sender': message.watched_by_sender # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุฑุณู„
747
+ })}\n\n"
748
+
749
+ time.sleep(1) # ุงู†ุชุธุฑ ุซุงู†ูŠุฉ ู‚ุจู„ ุงู„ุชุญู‚ู‚ ู…ุฑุฉ ุฃุฎุฑู‰
750
+
751
+ except Exception as e:
752
+ print(f"Error in SSE stream: {e}")
753
+ time.sleep(5) # ุงู†ุชุธุฑ 5 ุซูˆุงู†ู ู‚ุจู„ ุฅุนุงุฏุฉ ุงู„ู…ุญุงูˆู„ุฉ
754
+
755
+ return Response(stream_with_context(event_stream()), content_type='text/event-stream')
756
+
757
+
758
+ @app.route('/is_user_active/<int:user_id>')
759
+ @login_required
760
+ def is_user_active(user_id):
761
+ # ุชุญู‚ู‚ ู…ู…ุง ุฅุฐุง ูƒุงู† ุงู„ู…ุณุชุฎุฏู… ู†ุดุทู‹ุง (ู…ุซุงู„: ู…ู† ู…ุฌู…ูˆุนุฉ active_users)
762
+ is_active = user_id in active_users # active_users ู‡ูŠ ู…ุฌู…ูˆุนุฉ ุชุญุชูˆูŠ ุนู„ู‰ ู…ุนุฑูุงุช ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู†ุดุทูŠู†
763
+ print(is_active)
764
+ return jsonify({
765
+ 'active': is_active
766
+ })
767
+
768
+
769
+
770
+
771
+ # ุฏุงู„ุฉ ู„ุชุญุฏูŠุฏ ู†ูˆุน ุงู„ู…ู„ู (ุตูˆุฑุฉ ุฃูˆ ููŠุฏูŠูˆ)
772
+ def get_file_type(content, file_url):
773
+ if content: # ุฅุฐุง ูƒุงู† ู‡ู†ุงูƒ ู…ุญุชูˆู‰ ู†ุตูŠ
774
+ return 'text' # ู†ุต ุนุงุฏูŠ
775
+
776
+ # ุฅุฐุง ูƒุงู† content ูุงุฑุบู‹ุง ูˆูƒุงู† file_url ูŠุญุชูˆูŠ ุนู„ู‰ ุงู…ุชุฏุงุฏ ุตูˆุฑุฉ ุฃูˆ ููŠุฏูŠูˆ
777
+ if file_url:
778
+ _, ext = os.path.splitext(file_url) # ุงุณุชุฎุฑุงุฌ ุงู„ุงู…ุชุฏุงุฏ ู…ู† ุฑุงุจุท ุงู„ู…ู„ู
779
+ if ext.lower() in ['.png', '.jpg', '.jpeg', '.gif', '.bmp']: # ุงู…ุชุฏุงุฏุงุช ุงู„ุตูˆุฑ
780
+ return 'image' # ู†ูˆุน ุงู„ู…ู„ู ุตูˆุฑุฉ
781
+ elif ext.lower() in ['.mp4', '.avi', '.mov', '.mkv']: # ุงู…ุชุฏุงุฏุงุช ุงู„ููŠุฏูŠูˆ
782
+ return 'video' # ู†ูˆุน ุงู„ู…ู„ู ููŠุฏูŠูˆ
783
+
784
+ return 'text' # ุฅุฐุง ู„ู… ูŠุชู… ุงู„ุชุญุฏูŠุฏ (ู†ุต ุงูุชุฑุงุถูŠ)
785
+
786
+
787
+ @app.route('/get_notifications')
788
+ @login_required
789
+ def get_notifications():
790
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุฌู…ูŠุน ุงู„ุฑุณุงุฆู„ ุงู„ู…ุฑุณู„ุฉ ุฅู„ู‰ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ (ุงู„ุชูŠ ุงุณุชู„ู…ู‡ุง)
791
+ received_messages = Message.query.filter(
792
+ (Message.receiver_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุงุณุชู„ู…ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
793
+ (Message.sender_id != current_user.id) # ุงุณุชุจุนุงุฏ ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุฃุฑุณู„ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ุฅู„ู‰ ู†ูุณู‡
794
+ ).order_by(Message.created_at.desc()).all()
795
+
796
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุฌู…ูŠุน ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุฃุฑุณู„ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ (ูˆู„ู… ูŠุฑูŽ ุฃู† ุงู„ู…ุณุชู‚ุจู„ ุดุงู‡ุฏู‡ุง)
797
+ sent_messages = Message.query.filter(
798
+ (Message.sender_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุฃุฑุณู„ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
799
+ (Message.watched_by_sender == False) # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ู„ู… ูŠุฑูŽ ุงู„ู…ุฑุณู„ ุฃู† ุงู„ู…ุณุชู‚ุจู„ ุดุงู‡ุฏู‡ุง
800
+ ).order_by(Message.created_at.desc()).all()
801
+
802
+ # ุฏู…ุฌ ุงู„ุฑุณุงุฆู„ ุงู„ู…ุณุชู„ู…ุฉ ูˆุงู„ู…ุฑุณู„ุฉ
803
+ notifications = []
804
+
805
+ for message in received_messages:
806
+ sender = User.query.get(message.sender_id)
807
+ file_type = get_file_type(message.content, message.file_url) # ุชุญุฏูŠุฏ ู†ูˆุน ุงู„ู…ู„ู ุจู†ุงุกู‹ ุนู„ู‰ content ูˆ file_url
808
+ notifications.append({
809
+ 'id': message.id,
810
+ 'sender_id': message.sender_id,
811
+ 'sender_name': sender.username,
812
+ 'sender_email': sender.email,
813
+ 'sender_photo': url_for('static', filename=sender.profile_photo),
814
+ 'content': message.content,
815
+ 'file_url': message.file_url, # ุฅุถุงูุฉ ุฑุงุจุท ุงู„ู…ู„ู
816
+ 'created_at': message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
817
+ 'watched_by_receiver': message.watched_by_receiver, # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุณุชู‚ุจู„
818
+ 'is_received': True, # ุชู… ุงุณุชู„ุงู… ุงู„ุฑุณุงู„ุฉ
819
+ 'file_type': file_type # ุฅุถุงูุฉ ู†ูˆุน ุงู„ู…ู„ู
820
+ })
821
+
822
+ for message in sent_messages:
823
+ receiver = User.query.get(message.receiver_id)
824
+ file_type = get_file_type(message.content, message.file_url) # ุชุญุฏูŠุฏ ู†ูˆุน ุงู„ู…ู„ู ุจู†ุงุกู‹ ุนู„ู‰ content ูˆ file_url
825
+ notifications.append({
826
+ 'id': message.id,
827
+ 'receiver_id': message.receiver_id,
828
+ 'receiver_name': receiver.username,
829
+ 'receiver_email': receiver.email,
830
+ 'receiver_photo': url_for('static', filename=receiver.profile_photo),
831
+ 'content': message.content,
832
+ 'file_url': message.file_url, # ุฅุถุงูุฉ ุฑุงุจุท ุงู„ู…ู„ู
833
+ 'created_at': message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
834
+ 'watched_by_sender': message.watched_by_sender, # ุญุงู„ุฉ ู…ุดุงู‡ุฏุฉ ุงู„ู…ุฑุณู„
835
+ 'is_received': False, # ุชู… ุฅุฑุณุงู„ ุงู„ุฑุณุงู„ุฉ
836
+ 'file_type': file_type # ุฅุถุงูุฉ ู†ูˆุน ุงู„ู…ู„ู
837
+ })
838
+
839
+ return jsonify(notifications)
840
+
841
+ # ุชุญุฏูŠุซ ุฌู…ูŠุน ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ"
842
+ @app.route('/mark_all_as_read', methods=['POST'])
843
+ @login_required
844
+ def mark_all_as_read():
845
+ # ุชุญุฏูŠุซ ุฌู…ูŠุน ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ"
846
+ Message.query.filter_by(receiver_id=current_user.id, watched_by_receiver=False).update({'watched_by_receiver': False})
847
+ db.session.commit()
848
+ return jsonify({'success': True})
849
+
850
+ # ุจุซ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
851
+ @app.route('/stream_unread_count')
852
+ @login_required
853
+ def stream_unread_count():
854
+ def event_stream():
855
+ last_count = None # ู„ุชุชุจุน ุขุฎุฑ ุนุฏุฏ ุชู… ุฅุฑุณุงู„ู‡
856
+
857
+ while True:
858
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
859
+ unread_count = Message.query.filter(
860
+ (Message.receiver_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุงุณุชู„ู…ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
861
+ (Message.watched_by_receiver == False) # ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
862
+ ).count()
863
+
864
+ # ุฅุฑุณุงู„ ุงู„ุชุญุฏูŠุซ ูู‚ุท ุฅุฐุง ุชุบูŠุฑ ุงู„ุนุฏุฏ
865
+ if unread_count != last_count:
866
+ last_count = unread_count
867
+ yield f"data: {json.dumps({'unread_count': unread_count})}\n\n"
868
+
869
+ time.sleep(1) # ุงู†ุชุธุฑ ุซุงู†ูŠุฉ ู‚ุจู„ ุงู„ุชุญู‚ู‚ ู…ุฑุฉ ุฃุฎุฑู‰
870
+
871
+ return Response(stream_with_context(event_stream()), content_type='text/event-stream')
872
+
873
+
874
+ @app.route('/mark_message_as_seen/<int:message_id>', methods=['POST'])
875
+ @login_required
876
+ def mark_message_as_seen(message_id):
877
+ # ุชุญุฏูŠุซ ุญุงู„ุฉ ุงู„ุฑุณุงู„ุฉ ุฅู„ู‰ "ู…ู‚ุฑูˆุกุฉ" ู…ู† ู‚ุจู„ ุงู„ู…ุณุชู‚ุจู„
878
+ message = Message.query.get(message_id)
879
+ if message and message.receiver_id == current_user.id:
880
+ message.watched_by_receiver = True
881
+ message.last_updated = datetime.utcnow() # ุชุญุฏูŠุซ ูˆู‚ุช ุงู„ุชุนุฏูŠู„
882
+ db.session.commit()
883
+ return jsonify({'success': True})
884
+ return jsonify({'success': False}), 404
885
+
886
+
887
+
888
+ @app.route('/get_unread_count')
889
+ @login_required
890
+ def get_unread_count():
891
+ # ุญุณุงุจ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ ู„ู„ู…ุณุชู‚ุจู„
892
+ unread_receiver_count = Message.query.filter(
893
+ (Message.receiver_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุงุณุชู„ู…ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
894
+ (Message.watched_by_receiver == False) # ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
895
+ ).count()
896
+
897
+ # ุญุณุงุจ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ ู„ู„ู…ุฑุณู„
898
+ unread_sender_count = Message.query.filter(
899
+ (Message.sender_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุฃุฑุณู„ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
900
+ (Message.watched_by_sender == False) # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ู„ู… ูŠุฑูŽ ุงู„ู…ุฑุณู„ ุฃู† ุงู„ู…ุณุชู‚ุจู„ ุดุงู‡ุฏู‡ุง
901
+ ).count()
902
+
903
+ # ุฅุฌู…ุงู„ูŠ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
904
+ total_unread_count = unread_receiver_count + unread_sender_count
905
+
906
+ return jsonify({'unread_count': total_unread_count})
907
+
908
+
909
+
910
+ @app.route('/stream_message_updates/<int:user_id>')
911
+ @login_required
912
+ def stream_message_updates(user_id):
913
+ def event_stream():
914
+ last_update_time = datetime.utcnow() # ูˆู‚ุช ุจุฏุก ุงู„ุงุชุตุงู„
915
+
916
+ while True:
917
+ # ุงู„ุจุญุซ ุนู† ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุชู‡ู… ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ (ู…ุฑุณู„ุฉ ุฃูˆ ู…ุณุชู„ู…ุฉ)
918
+ messages = Message.query.filter(
919
+ (
920
+ (Message.sender_id == current_user.id) | # ุงู„ุฑุณุงุฆู„ ุงู„ู…ุฑุณู„ุฉ ู…ู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
921
+ (Message.receiver_id == current_user.id) # ุงู„ุฑุณุงุฆู„ ุงู„ู…ุณุชู„ู…ุฉ ู„ู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
922
+ ) &
923
+ (Message.last_updated > last_update_time) # ุงู„ุชุญุฏูŠุซุงุช ุงู„ุฌุฏูŠุฏุฉ ุจุนุฏ ุขุฎุฑ ุชุญู‚ู‚
924
+ ).order_by(Message.last_updated.asc()).all()
925
+
926
+ if messages:
927
+ last_update_time = messages[-1].last_updated # ุชุญุฏูŠุซ ูˆู‚ุช ุงู„ุชุญู‚ู‚ ุงู„ุฃุฎูŠ๏ฟฝ๏ฟฝ
928
+
929
+ for message in messages:
930
+ yield f"data: {json.dumps({
931
+ 'message_id': message.id,
932
+ 'sender_id': message.sender_id,
933
+ 'receiver_id': message.receiver_id,
934
+ 'watched_by_receiver': message.watched_by_receiver,
935
+ 'watched_by_sender': message.watched_by_sender
936
+ })}\n\n"
937
+
938
+ time.sleep(0.5) # ุชุญู‚ู‚ ูƒู„ 0.5 ุซุงู†ูŠุฉ ู„ุชุญุณูŠู† ุงู„ุฃุฏุงุก
939
+
940
+ return Response(stream_with_context(event_stream()), content_type='text/event-stream')
941
+
942
+ @app.route('/leave_chat', methods=['POST'])
943
+ @login_required
944
+ def leave_chat():
945
+ # ุณุฌู„ ุนู†ุฏ ุจุฏุก ุงู„ุฏุงู„ุฉ
946
+ print(f"Attempting to leave chat for user {current_user.id}")
947
+
948
+ # ุฅุฒุงู„ุฉ ุงู„ู…ุณุชุฎุฏู… ู…ู† ู‚ุงุฆู…ุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู†ุดุทูŠู†
949
+ active_users.discard(current_user.id)
950
+
951
+ # ุณุฌู„ ุจุนุฏ ุงู„ุชุนุฏูŠู„ ุนู„ู‰ ุงู„ู‚ุงุฆู…ุฉ
952
+ print(f"User {current_user.id} left the chat. Active users: {active_users}")
953
+
954
+ return jsonify({'success': True})
955
+
956
+
957
+ @app.route('/join_chat', methods=['POST'])
958
+ @login_required
959
+ def join_chat():
960
+ # ุฅุถุงูุฉ ุงู„ู…ุณุชุฎุฏู… ุฅู„ู‰ ู‚ุงุฆู…ุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู†ุดุทูŠู†
961
+ active_users.add(current_user.id)
962
+ print(active_users)
963
+ return jsonify({'success': True})
964
+
965
+
966
+
967
+ @app.route('/mark_all_as_seen', methods=['POST'])
968
+ @login_required
969
+ def mark_all_as_seen():
970
+ receiver_id = request.form.get('receiver_id')
971
+
972
+ # ุชุญุฏูŠุซ ุงู„ุฑุณุงุฆู„ ุงู„ู…ุฑุณู„ุฉ ู…ู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ุฅู„ู‰ ุงู„ู…ุณุชู‚ุจู„ (watched_by_receiver)
973
+ Message.query.filter(
974
+ (Message.sender_id == current_user.id) &
975
+ (Message.receiver_id == receiver_id) &
976
+ (Message.watched_by_receiver == False)
977
+ ).update({'watched_by_receiver': True})
978
+
979
+ # ุชุญุฏูŠุซ ุงู„ุฑุณุงุฆู„ ุงู„ู…ุณุชู„ู…ุฉ ู…ู† ุงู„ู…ุณุชู‚ุจู„ (watched_by_sender)
980
+ Message.query.filter(
981
+ (Message.sender_id == receiver_id) &
982
+ (Message.receiver_id == current_user.id) &
983
+ (Message.watched_by_sender == False)
984
+ ).update({'watched_by_sender': True})
985
+
986
+ db.session.commit()
987
+ return jsonify({'success': True})
988
+
989
+
990
+
991
+ @app.route('/getchat')
992
+ @login_required
993
+ def get_chat():
994
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุขุฎุฑ ุงู„ุฑุณุงุฆู„ ุจูŠู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ูˆุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุขุฎุฑูŠู†
995
+ subquery = db.session.query(
996
+ db.case(
997
+ (Message.sender_id > Message.receiver_id, Message.sender_id),
998
+ else_=Message.receiver_id
999
+ ).label('user1'),
1000
+ db.case(
1001
+ (Message.sender_id > Message.receiver_id, Message.receiver_id),
1002
+ else_=Message.sender_id
1003
+ ).label('user2'),
1004
+ db.func.max(Message.id).label('last_message_id')
1005
+ ).filter(
1006
+ (Message.receiver_id == current_user.id) | # ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ู‡ูˆ ุงู„ู…ุณุชู‚ุจู„
1007
+ (Message.sender_id == current_user.id) # ุฃูˆ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ู‡ูˆ ุงู„ู…ุฑุณู„
1008
+ ).group_by('user1', 'user2').subquery()
1009
+
1010
+ # ุฌู„ุจ ุชูุงุตูŠู„ ุงู„ุฑุณุงุฆู„
1011
+ latest_messages = Message.query.join(
1012
+ subquery, Message.id == subquery.c.last_message_id
1013
+ ).order_by(Message.created_at.desc()).all()
1014
+
1015
+ # ุจู†ุงุก ุงู„ุจูŠุงู†ุงุช ู„ู„ุฅุฑุฌุงุน
1016
+ chats = []
1017
+ for message in latest_messages:
1018
+ # ุชุญุฏูŠุฏ ุงู„ู…ุณุชุฎุฏู… ุงู„ุขุฎุฑ
1019
+ if message.sender_id == current_user.id:
1020
+ other_user = User.query.get(message.receiver_id)
1021
+ else:
1022
+ other_user = User.query.get(message.sender_id)
1023
+
1024
+ # ุญุณุงุจ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
1025
+ unread_count = Message.query.filter(
1026
+ (Message.receiver_id == current_user.id) & # ุงู„ุฑุณุงุฆู„ ุงู„ุชูŠ ุงุณุชู„ู…ู‡ุง ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1027
+ (Message.sender_id == other_user.id) & # ุงู„ุฑุณุงุฆู„ ู…ู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุขุฎุฑ
1028
+ (Message.watched_by_receiver == False) # ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
1029
+ ).count()
1030
+
1031
+ # ุชุญุฏูŠุฏ ู†ูˆุน ุงู„ู…ุญุชูˆู‰ ุจู†ุงุกู‹ ุนู„ู‰ ุงู„ุฑุณุงู„ุฉ
1032
+ last_message = message.content
1033
+ message_type = None # ู†ูˆุน ุงู„ุฑุณุงู„ุฉ (ู†ุตุŒ ููŠุฏูŠูˆุŒ ุตูˆุฑุฉ)
1034
+
1035
+ # ุชุญู‚ู‚ ู…ู† ู†ูˆุน ุงู„ู…ุญุชูˆู‰
1036
+ if last_message:
1037
+ # ุฅุฐุง ูƒุงู† content ู†ุตูŠุงู‹ ูˆู„ูŠุณ ุตูˆุฑุฉ
1038
+ if not message.file_url or not message.file_url.lower().endswith(('jpg', 'jpeg', 'png')):
1039
+ message_type = 'text'
1040
+ elif message.file_url:
1041
+ # ุฅุฐุง ูƒุงู† content ูุงุฑุบุงู‹ ูˆูƒุงู† file_url ุจู‡ ููŠ ุขุฎุฑู‡ ุงู…ุชุฏุงุฏ ููŠุฏูŠูˆ
1042
+ if message.file_url.lower().endswith(('mp4', 'avi', 'mov')):
1043
+ message_type = 'video'
1044
+ # ุฅุฐุง ูƒุงู† content ูุงุฑุบุงู‹ ูˆูƒุงู† file_url ุจู‡ ููŠ ุขุฎุฑู‡ ุงู…ุชุฏุงุฏ ุตูˆุฑุฉ
1045
+ elif message.file_url.lower().endswith(('jpg', 'jpeg', 'png')):
1046
+ message_type = 'image'
1047
+
1048
+ # ุฅุถุงูุฉ ุจูŠุงู†ุงุช ุงู„ู…ุญุงุฏุซุฉ
1049
+ chats.append({
1050
+ 'id': message.id,
1051
+ 'user_id': other_user.id,
1052
+ 'user_name': other_user.username,
1053
+ 'user_email': other_user.email,
1054
+ 'user_photo': url_for('static', filename=other_user.profile_photo),
1055
+ 'last_message': last_message,
1056
+ 'message_type': message_type, # ุฅุถุงูุฉ ู†ูˆุน ุงู„ุฑุณุงู„ุฉ
1057
+ 'unread_count': unread_count, # ุฅุถุงูุฉ ุนุฏุฏ ุงู„ุฑุณุงุฆู„ ุบูŠุฑ ุงู„ู…ู‚ุฑูˆุกุฉ
1058
+ 'last_updated': message.created_at.strftime('%Y-%m-%d %H:%M:%S'),
1059
+ 'is_sender': message.sender_id == current_user.id # ู‡ู„ ุงู„ู…ุฑุณู„ ู‡ูˆ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠุŸ
1060
+ })
1061
+
1062
+ return jsonify(chats)
1063
+
1064
+ @app.route('/search_users')
1065
+ def search_users():
1066
+ query = request.args.get('q', '')
1067
+ if query:
1068
+ # ุงู„ุจุญุซ ููŠ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
1069
+ users = User.query.filter(User.username.like(f'%{query}%')).all()
1070
+ # ุชุฌู‡ูŠุฒ ุงู„ุจูŠุงู†ุงุช ู„ุนุฑุถู‡ุง ููŠ ุงู„ูˆุงุฌู‡ุฉ
1071
+ results = [{
1072
+ 'username': user.username,
1073
+ 'profile_photo': user.profile_photo
1074
+ } for user in users]
1075
+ return jsonify(results)
1076
+ return jsonify([]) # ููŠ ุญุงู„ุฉ ุนุฏู… ูˆุฌูˆุฏ ู†ุชุงุฆุฌ
1077
+
1078
+ from flask_login import current_user # ุชุฃูƒุฏ ู…ู† ุงุณุชูŠุฑุงุฏ current_user
1079
+
1080
+ @app.route('/artical', methods=['GET'])
1081
+ def artical_page():
1082
+ if not current_user.is_authenticated:
1083
+ return redirect(url_for('login'))
1084
+
1085
+ # ุฌู„ุจ ุงู„ู…ู‚ุงู„ุงุช ุงู„ุชูŠ ุชุชุทุงุจู‚ ู…ุน profession ุงู„ุฎุงุต ุจุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1086
+ articles = Article.query.filter_by(profession_user=current_user.profession).order_by(Article.created_at.desc()).all()
1087
+
1088
+ return render_template('artical.html', articles=articles)
1089
+
1090
+ @app.route('/create', methods=['POST'])
1091
+ def create_article():
1092
+ data = request.get_json()
1093
+ title = data['title']
1094
+
1095
+ # ุฌู„ุจ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ู…ู† ุงู„ุฌู„ุณุฉ
1096
+ if current_user.is_authenticated: # ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ู…ุณุฌู„ ุงู„ุฏุฎูˆู„
1097
+ user = current_user.username # ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1098
+ id_user = current_user.id # ู…ุนุฑู ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1099
+ photo_user = current_user.profile_photo # ุตูˆุฑุฉ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1100
+ profession_user = current_user.profession
1101
+ else:
1102
+ return jsonify({"error": "ูŠุฌุจ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ู„ุฅู†ุดุงุก ู…ู‚ุงู„"}), 401
1103
+
1104
+ # ุงุณุชุฎุฏุงู… ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ู„ุฅู†ุดุงุก ู…ุญุชูˆู‰ ุงู„ู…ู‚ุงู„
1105
+ client = Client()
1106
+ stream = client.chat.completions.create(
1107
+ model="gpt-4o-mini",
1108
+ messages=[{"role": "user", "content": f"Write an article about {title}"}],
1109
+ stream=True,
1110
+ web_search=False
1111
+ )
1112
+
1113
+ # ุฌู…ุน ู†ุต ุงู„ู…ู‚ุงู„
1114
+ content_markdown = ""
1115
+ for chunk in stream:
1116
+ if chunk.choices[0].delta.content:
1117
+ content_markdown += chunk.choices[0].delta.content
1118
+
1119
+ # ุชุญูˆูŠู„ Markdown ุฅู„ู‰ HTML ุจุงุณุชุฎุฏุงู… ุฅุถุงูุงุช ู„ุชุญุณูŠู† ุงู„ู†ุชูŠุฌุฉ
1120
+ extensions = [
1121
+ 'fenced_code', # ู„ุฏุนู… code blocks
1122
+ 'tables', # ู„ุฏุนู… ุงู„ุฌุฏุงูˆู„
1123
+ 'toc', # ู„ุฏุนู… ุฌุฏูˆู„ ุงู„ู…ุญุชูˆูŠุงุช
1124
+ 'nl2br', # ู„ุชุญูˆูŠู„ ุงู„ุฃุณุทุฑ ุงู„ุฌุฏูŠุฏุฉ ุฅู„ู‰ <br>
1125
+ ]
1126
+ content_html = markdown.markdown(content_markdown, extensions=extensions) # ุชู… ุงู„ุชุตุญูŠุญ ู‡ู†ุง
1127
+
1128
+ # ู…ุนุงู„ุฌุฉ ุงู„ุฃูƒูˆุงุฏ ุงู„ุจุฑู…ุฌูŠุฉ ุจุดูƒู„ ุตุญูŠุญ
1129
+ # ุงู„ุจุญุซ ุนู† ุงู„ุฃูƒูˆุงุฏ ุงู„ุจุฑู…ุฌูŠุฉ ุงู„ุชูŠ ุชุจุฏุฃ ุจู€ ```language ูˆุฅุบู„ุงู‚ู‡ุง ุจู€ ```
1130
+ content_html = re.sub(r'```(\w+)', r'<pre><code class="\1">', content_html)
1131
+ content_html = content_html.replace('```', '</code></pre>')
1132
+
1133
+ # ุญูุธ ุงู„ู…ู‚ุงู„ ููŠ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
1134
+ article = Article(title=title, content=content_html, user=user, id_user=id_user, photo_user=photo_user , profession_user = profession_user)
1135
+ db.session.add(article)
1136
+ db.session.commit()
1137
+
1138
+ # ุงู„ุฑุฏ ุนู„ู‰ ุงู„ุนู…ูŠู„ ุจุฅุฑุฌุงุน ุงู„ุจูŠุงู†ุงุช ุงู„ุชูŠ ุชู… ุฅู†ุดุงุคู‡ุง
1139
+ return jsonify({
1140
+ "title": article.title,
1141
+ "content": article.content,
1142
+ "user": article.user,
1143
+ "created_at": article.created_at.strftime('%Y-%m-%d %H:%M:%S'),
1144
+ "photo_user": f"/static/{article.photo_user}",
1145
+ "id_user": id_user
1146
+ })
1147
+
1148
+ @app.route('/current_user_id', methods=['GET'])
1149
+ def get_current_user_id():
1150
+ if current_user.is_authenticated: # ุงู„ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ู…ุณุฌู„ ุงู„ุฏุฎูˆู„
1151
+ print(f"ID ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ: {current_user.id}") # ุนุฑุถ id ุงู„ู…ุณุชุฎุฏู… ููŠ ุงู„ุทุฑููŠุฉ
1152
+ return jsonify({'id': current_user.id}), 200 # ุฅุฑุฌุงุน id ูู‚ุท
1153
+ else:
1154
+ print("ุงู„ู…ุณุชุฎุฏู… ุบูŠุฑ ู…ุณุฌู„ ุงู„ุฏุฎูˆู„") # ุนุฑุถ ุฑุณุงู„ุฉ ููŠ ุงู„ุทุฑููŠุฉ ุฅุฐุง ู„ู… ูŠูƒู† ุงู„ู…ุณุชุฎุฏู… ู…ุณุฌู„ ุงู„ุฏุฎูˆู„
1155
+ return jsonify({'error': 'User not authenticated'}), 401
1156
+
1157
+
1158
+
1159
+
1160
+
1161
+ def calculate_frame_contrast(frame):
1162
+ """
1163
+ ุญุณุงุจ ุงู„ุชุจุงูŠู† ููŠ ุงู„ุฅุทุงุฑ ุจุงุณุชุฎุฏุงู… Pillow
1164
+ """
1165
+ image = Image.fromarray(frame)
1166
+ gray_image = image.convert('L')
1167
+ img_array = np.array(gray_image)
1168
+ contrast = np.std(img_array)
1169
+ return contrast
1170
+
1171
+ def get_video_duration(video_path):
1172
+ """
1173
+ ุงู„ุญุตูˆู„ ุนู„ู‰ ู…ุฏุฉ ุงู„ููŠุฏูŠูˆ ุจุงู„ุซูˆุงู†ูŠ
1174
+ """
1175
+ return len(list(iio.imiter(video_path)))
1176
+
1177
+ def generate_thumbnail(video_path):
1178
+ """
1179
+ ุฅู†ุดุงุก ุตูˆุฑุฉ ู…ุตุบุฑุฉ ู…ู† ู…ู„ู ููŠุฏูŠูˆ ุจุงุณุชุฎุฏุงู… 3 ุฅุทุงุฑุงุช ุนุดูˆุงุฆูŠุฉ
1180
+ """
1181
+ thumbnail_filename = os.path.splitext(os.path.basename(video_path))[0] + '_thumbnail.jpg'
1182
+ thumbnail_path = os.path.join(app.config['THUMBNAIL_FOLDER'], thumbnail_filename)
1183
+
1184
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ู…ุฏุฉ ุงู„ููŠุฏูŠูˆ
1185
+ video_length = get_video_duration(video_path)
1186
+
1187
+ # ุงุฎุชูŠุงุฑ 3 ุฃูˆู‚ุงุช ุนุดูˆุงุฆูŠุฉ ู…ุชุจุงุนุฏุฉ
1188
+ frame_positions = sorted([
1189
+ int(video_length * 0.2 + random.randint(0, int(video_length * 0.2))),
1190
+ int(video_length * 0.5 + random.randint(0, int(video_length * 0.2))),
1191
+ int(video_length * 0.8 + random.randint(0, int(video_length * 0.2)))
1192
+ ])
1193
+
1194
+ max_contrast = 0
1195
+ best_frame = None
1196
+
1197
+ # ู‚ุฑุงุกุฉ ุงู„ููŠุฏูŠูˆ
1198
+ reader = iio.imiter(video_path)
1199
+
1200
+ # ู…ุนุงู„ุฌุฉ ุงู„ุฅุทุงุฑุงุช ุงู„ู…ุฎุชุงุฑุฉ ูู‚ุท
1201
+ for i, frame in enumerate(reader):
1202
+ if i in frame_positions:
1203
+ contrast = calculate_frame_contrast(frame)
1204
+
1205
+ if contrast > max_contrast:
1206
+ max_contrast = contrast
1207
+ best_frame = frame
1208
+
1209
+ if best_frame is not None:
1210
+ # ุชุญูˆูŠู„ numpy array ุฅู„ู‰ ุตูˆุฑุฉ Pillow
1211
+ image = Image.fromarray(best_frame)
1212
+
1213
+ # ุฃุจุนุงุฏ ุงู„ุตูˆุฑุฉ ุงู„ู…ุตุบุฑุฉ ุงู„ู…ุทู„ูˆุจุฉ
1214
+ target_width, target_height = 1280, 720
1215
+
1216
+ # ุญุณุงุจ ู†ุณุจุฉ ุงู„ุนุฑุถ ุฅู„ู‰ ุงู„ุงุฑุชูุงุน
1217
+ original_width, original_height = image.size
1218
+ aspect_ratio_original = original_width / original_height
1219
+ aspect_ratio_target = target_width / target_height
1220
+
1221
+ # ุชุบูŠูŠุฑ ุงู„ุญุฌู… ู…ุน ุงู„ุญูุงุธ ุนู„ู‰ ุงู„ู†ุณุจุฉ
1222
+ if aspect_ratio_original > aspect_ratio_target:
1223
+ new_width = target_width
1224
+ new_height = int(target_width / aspect_ratio_original)
1225
+ else:
1226
+ new_height = target_height
1227
+ new_width = int(target_height * aspect_ratio_original)
1228
+
1229
+ # ุชุบูŠูŠุฑ ุญุฌู… ุงู„ุตูˆุฑุฉ
1230
+ resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
1231
+
1232
+ # ุญุณุงุจ ุงู„ู„ูˆู† ุงู„ุณุงุฆุฏ
1233
+ img_array = np.array(image)
1234
+ dominant_color = tuple(map(int, np.mean(img_array, axis=(0, 1))))
1235
+
1236
+ # ุฅู†ุดุงุก ุตูˆุฑุฉ ุฌุฏูŠุฏุฉ ุจุงู„ุฎู„ููŠุฉ
1237
+ thumbnail = Image.new('RGB', (target_width, target_height), dominant_color)
1238
+
1239
+ # ุญุณุงุจ ุงู„ุฅุฒุงุญุฉ ู„ูˆุถุน ุงู„ุตูˆุฑุฉ ููŠ ุงู„ู…ู†ุชุตู
1240
+ x_offset = (target_width - new_width) // 2
1241
+ y_offset = (target_height - new_height) // 2
1242
+
1243
+ # ูˆุถุน ุงู„ุตูˆุฑุฉ ููŠ ุงู„ู…ู†ุชุตู
1244
+ thumbnail.paste(resized_image, (x_offset, y_offset))
1245
+
1246
+ # ุญูุธ ุงู„ุตูˆุฑุฉ ุงู„ู…ุตุบุฑุฉ
1247
+ thumbnail.save(thumbnail_path, 'JPEG', quality=95)
1248
+
1249
+ return thumbnail_path
1250
+
1251
+
1252
+
1253
+ @app.route('/videos', methods=['GET', 'POST'])
1254
+ @login_required
1255
+ def videos():
1256
+ if request.method == 'POST':
1257
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ุนู†ูˆุงู† ู…ู† ุงู„ู…ุณุชุฎุฏู…
1258
+ title = request.form.get('title')
1259
+
1260
+ # ุชุฎุฒูŠู† ุงู„ูุฆุฉ ุงู„ุญุงู„ูŠุฉ ู„ู„ู…ุณุชุฎุฏู… ูˆู‚ุช ุงู„ุฑูุน
1261
+ category = current_user.profession
1262
+
1263
+ # ุงู„ุชุนุงู…ู„ ู…ุน ุฑูุน ุงู„ููŠุฏูŠูˆ
1264
+ video = request.files.get('video')
1265
+ thumbnail = request.files.get('thumbnail')
1266
+
1267
+ if video:
1268
+ # ู…ุนุงู„ุฌุฉ ุงู„ููŠุฏูŠูˆ
1269
+ video_filename = secure_filename(video.filename)
1270
+ video_path = os.path.join(app.config['UPLOAD_FOLDER2'], video_filename)
1271
+ video.save(video_path)
1272
+
1273
+ # ู…ุนุงู„ุฌุฉ ุงู„ุตูˆุฑุฉ ุงู„ู…ุตุบุฑุฉ
1274
+ thumbnail_path = None
1275
+ if thumbnail:
1276
+ thumbnail_filename = secure_filename(thumbnail.filename)
1277
+ thumbnail_path = os.path.join(app.config['THUMBNAIL_FOLDER'], thumbnail_filename)
1278
+ thumbnail.save(thumbnail_path)
1279
+ else:
1280
+ # ุฅู†ุดุงุก ุตูˆุฑุฉ ู…ุตุบุฑุฉ ุชู„ู‚ุงุฆูŠู‹ุง
1281
+ thumbnail_path = generate_thumbnail(video_path)
1282
+
1283
+ # ุฅู†ุดุงุก ูƒุงุฆู† ููŠุฏูŠูˆ ุฌุฏูŠุฏ ููŠ ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช
1284
+ new_video = Video(
1285
+ video_path=video_path,
1286
+ thumbnail_path=thumbnail_path,
1287
+ uploader_name=current_user.username,
1288
+ user_id=current_user.id,
1289
+ uploader_image=current_user.profile_photo,
1290
+ title=title, # ุฅุถุงูุฉ ุงู„ุนู†ูˆุงู†
1291
+ category=category # ุชุฎุฒูŠู† ุงู„ูุฆุฉ ุจู†ุงุกู‹ ุนู„ู‰ ุญุงู„ุฉ ุงู„ู…ุณุชุฎุฏู… ุนู†ุฏ ุฑูุน ุงู„ููŠุฏูŠูˆ
1292
+ )
1293
+ db.session.add(new_video)
1294
+ db.session.commit()
1295
+
1296
+ flash('ุชู… ุฑูุน ุงู„ููŠุฏูŠูˆ ุจู†ุฌุงุญ!', 'success')
1297
+ return redirect(url_for('videos'))
1298
+
1299
+ # ุนู†ุฏ ุฌู„ุจ ุงู„ููŠุฏูŠูˆู‡ุงุช ูŠุชู… ุนุฑุถ ุตูุญุฉ ูู‚ุท ุจุฏูˆู† ุชุญู…ูŠู„ ุงู„ููŠุฏูŠูˆู‡ุงุช ููˆุฑุงู‹
1300
+ return render_template('videos.html')
1301
+
1302
+
1303
+ @app.route('/get_videos', methods=['GET'])
1304
+ @login_required
1305
+ def get_videos():
1306
+ current_user_id = current_user.id # ุงู„ุญุตูˆู„ ุนู„ู‰ ู…ุนุฑู ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1307
+
1308
+ # ุชุตููŠุฉ ู…ู‚ุงุทุน ุงู„ููŠุฏูŠูˆ ุจู†ุงุกู‹ ุนู„ู‰ ูุฆุฉ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠุฉ
1309
+ videos = Video.query.filter_by(category=current_user.profession).order_by(Video.upload_time.desc()).all()
1310
+
1311
+ # ุชุญูˆูŠู„ ุงู„ุจูŠุงู†ุงุช ุฅู„ู‰ JSON ุจุญูŠุซ ูŠู…ูƒู† ุฅุฑุณุงู„ู‡ุง ุฅู„ู‰ ุงู„ู…ุชุตูุญ
1312
+ videos_data = [{
1313
+ 'id': video.id,
1314
+ 'title': video.title,
1315
+ 'thumbnail': video.thumbnail_path,
1316
+ 'uploader_name': video.uploader_name,
1317
+ 'upload_time': video.upload_time.strftime('%Y-%m-%d %H:%M:%S'),
1318
+ 'uploader_image': url_for('static', filename=video.uploader_image),
1319
+ 'category': video.category, # ุฅุถุงูุฉ ุงู„ูุฆุฉ
1320
+ 'video_path': video.video_path, # ู…ุณุงุฑ ุงู„ููŠุฏูŠูˆ
1321
+ 'is_owner': video.user_id == current_user_id # ุชุญุฏูŠุฏ ุฅุฐุง ูƒุงู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ู‡ูˆ ุตุงุญุจ ุงู„ููŠุฏูŠูˆ
1322
+ } for video in videos]
1323
+
1324
+ return jsonify(videos_data)
1325
+
1326
+
1327
+
1328
+
1329
+
1330
+ # Route ู„ุชุนุฏูŠู„ ุนู†ูˆุงู† ุงู„ููŠุฏูŠูˆ
1331
+ @app.route('/edit_video/<int:video_id>', methods=['POST'])
1332
+ def edit_video(video_id):
1333
+ video = Video.query.get_or_404(video_id)
1334
+ data = request.get_json()
1335
+ new_title = data.get('title')
1336
+
1337
+ if new_title:
1338
+ video.title = new_title
1339
+ db.session.commit()
1340
+ return jsonify({'success': True})
1341
+ return jsonify({'success': False, 'message': 'ุงู„ุนู†ูˆุงู† ุบูŠุฑ ุตุงู„ุญ'}), 400
1342
+
1343
+ # Route ู„ุญุฐู ุงู„ููŠุฏูŠูˆ
1344
+ @app.route('/delete_video/<int:video_id>', methods=['DELETE'])
1345
+ @login_required
1346
+ def delete_video(video_id):
1347
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ููŠุฏูŠูˆ ู…ูˆุฌูˆุฏ
1348
+ video = Video.query.get_or_404(video_id)
1349
+
1350
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ ู‡ูˆ ุตุงุญุจ ุงู„ููŠุฏูŠูˆ
1351
+ if video.user_id != current_user.id:
1352
+ return jsonify({'success': False, 'message': 'ุบูŠุฑ ู…ุตุฑุญ ู„ูƒ ุจุญุฐู ู‡ุฐุง ุงู„ููŠุฏูŠูˆ'}), 403
1353
+
1354
+ # ุญุฐู ุฌู…ูŠุน ุงู„ุณุฌู„ุงุช ุงู„ู…ุฑุชุจุทุฉ ุจุงู„ููŠุฏูŠูˆ ู…ู† ุฌุฏูˆู„ playlist_video
1355
+ PlaylistVideo.query.filter_by(video_id=video_id).delete()
1356
+
1357
+ # ุญุฐู ู…ู„ู ุงู„ููŠุฏูŠูˆ ู…ู† ุงู„ู…ุฌู„ุฏ
1358
+ if video.video_path:
1359
+ video_file_name = os.path.basename(video.video_path) # ุงู„ุญุตูˆู„ ุนู„ู‰ ุงุณู… ุงู„ู…ู„ู ูู‚ุท
1360
+ video_file_path = os.path.join(app.config['UPLOAD_FOLDER2'], video_file_name)
1361
+ print(f"Video file path: {video_file_path}") # ุทุจุงุนุฉ ู…ุณุงุฑ ู…ู„ู ุงู„ููŠุฏูŠูˆ
1362
+ if os.path.exists(video_file_path):
1363
+ os.remove(video_file_path)
1364
+ else:
1365
+ print("Video file does not exist!")
1366
+
1367
+ # ุญุฐู ู…ู„ู ุงู„ุตูˆุฑุฉ ุงู„ู…ุตุบุฑุฉ ู…ู† ุงู„ู…ุฌู„ุฏ
1368
+ if video.thumbnail_path:
1369
+ thumbnail_file_name = os.path.basename(video.thumbnail_path) # ุงู„ุญุตูˆู„ ุนู„ู‰ ุงุณู… ุงู„ู…ู„ู ูู‚ุท
1370
+ thumbnail_file_path = os.path.join(app.config['THUMBNAIL_FOLDER'], thumbnail_file_name)
1371
+ print(f"Thumbnail file path: {thumbnail_file_path}") # ุทุจุงุนุฉ ู…ุณุงุฑ ุงู„ุตูˆุฑุฉ ุงู„ู…ุตุบุฑุฉ
1372
+ if os.path.exists(thumbnail_file_path):
1373
+ os.remove(thumbnail_file_path)
1374
+ else:
1375
+ print("Thumbnail file does not exist!")
1376
+
1377
+ # ุญุฐู ุงู„ููŠุฏูŠูˆ ู…ู† ุฌุฏูˆู„ ุงู„ููŠุฏูŠูˆู‡ุงุช
1378
+ db.session.delete(video)
1379
+ db.session.commit()
1380
+
1381
+ return jsonify({'success': True})
1382
+
1383
+
1384
+ # Route ู„ุฅุถุงูุฉ ุงู„ููŠุฏูŠูˆ ุฅู„ู‰ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„
1385
+ # Route ู„ุฌู„ุจ ู‚ูˆุงุฆู… ุงู„ุชุดุบูŠู„
1386
+ @app.route('/get_playlists_with_video_status/<int:video_id>', methods=['GET'])
1387
+ @login_required
1388
+ def get_playlists_with_video_status(video_id):
1389
+ playlists = Playlist.query.filter_by(user_id=current_user.id).all()
1390
+ playlists_data = []
1391
+ for playlist in playlists:
1392
+ video_exists = PlaylistVideo.query.filter_by(playlist_id=playlist.id, video_id=video_id).first() is not None
1393
+ playlists_data.append({
1394
+ 'id': playlist.id,
1395
+ 'name': playlist.name,
1396
+ 'is_public': playlist.is_public,
1397
+ 'video_exists': video_exists,
1398
+ })
1399
+ return jsonify(playlists_data)
1400
+
1401
+
1402
+ @app.route('/remove_video_from_playlist/<int:playlist_id>/<int:video_id>', methods=['DELETE'])
1403
+ @login_required
1404
+ def remove_video_from_playlist(playlist_id, video_id):
1405
+ playlist_video = PlaylistVideo.query.filter_by(playlist_id=playlist_id, video_id=video_id).first()
1406
+ if playlist_video:
1407
+ db.session.delete(playlist_video)
1408
+ db.session.commit()
1409
+ return jsonify({'success': True})
1410
+ return jsonify({'success': False, 'message': 'ุงู„ููŠุฏูŠูˆ ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ู‚ุงุฆู…ุฉ'}), 404
1411
+
1412
+ # Route ู„ุฅู†ุดุงุก ู‚ุงุฆู…ุฉ ุชุดุบูŠู„ ุฌุฏูŠุฏุฉ
1413
+ @app.route('/create_playlist', methods=['POST'])
1414
+ @login_required
1415
+ def create_playlist():
1416
+ data = request.get_json()
1417
+ new_playlist = Playlist(
1418
+ name=data['name'],
1419
+ is_public=data['is_public'],
1420
+ user_id=current_user.id
1421
+ )
1422
+ db.session.add(new_playlist)
1423
+ db.session.commit()
1424
+ return jsonify({'success': True})
1425
+
1426
+ @app.route('/add_video_to_playlist/<int:playlist_id>/<int:video_id>', methods=['POST'])
1427
+ @login_required
1428
+ def add_video_to_playlist(playlist_id, video_id):
1429
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ููŠุฏูŠูˆ ูˆุงู„ู‚ุงุฆู…ุฉ ู…ูˆุฌูˆุฏุฉ
1430
+ video = Video.query.get_or_404(video_id)
1431
+ playlist = Playlist.query.get_or_404(playlist_id)
1432
+
1433
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ูŠู…ู„ูƒ ุงู„ู‚ุงุฆู…ุฉ
1434
+ if playlist.user_id != current_user.id:
1435
+ return jsonify({'success': False, 'message': 'ุบูŠุฑ ู…ุตุฑุญ ู„ูƒ ุจุฅุถุงูุฉ ุงู„ููŠุฏูŠูˆ ุฅู„ู‰ ู‡ุฐู‡ ุงู„ู‚ุงุฆู…ุฉ'}), 403
1436
+
1437
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ููŠุฏูŠูˆ ุบูŠุฑ ู…ูˆุฌูˆุฏ ุจุงู„ูุนู„ ููŠ ุงู„ู‚ุงุฆู…ุฉ
1438
+ existing_playlist_video = PlaylistVideo.query.filter_by(playlist_id=playlist_id, video_id=video_id).first()
1439
+ if existing_playlist_video:
1440
+ return jsonify({'success': False, 'message': 'ุงู„ููŠุฏูŠูˆ ู…ูˆุฌูˆุฏ ุจุงู„ูุนู„ ููŠ ุงู„ู‚ุงุฆู…ุฉ'}), 400
1441
+
1442
+ # ุฅุถุงูุฉ ุงู„ููŠุฏูŠูˆ ุฅู„ู‰ ุงู„ู‚ุงุฆู…ุฉ
1443
+ playlist_video = PlaylistVideo(playlist_id=playlist_id, video_id=video_id)
1444
+ db.session.add(playlist_video)
1445
+ db.session.commit()
1446
+ return jsonify({'success': True})
1447
+
1448
+
1449
+
1450
+ @app.route('/delete_playlist/<int:playlist_id>', methods=['DELETE'])
1451
+ @login_required
1452
+ def delete_playlist(playlist_id):
1453
+ playlist = Playlist.query.get_or_404(playlist_id)
1454
+
1455
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ูŠู…ู„ูƒ ุงู„ู‚ุงุฆู…ุฉ
1456
+ if playlist.user_id != current_user.id:
1457
+ return jsonify({'success': False, 'message': 'ุบูŠุฑ ู…ุตุฑุญ ู„ูƒ ุจุญุฐู ู‡ุฐู‡ ุงู„ู‚ุงุฆู…ุฉ'}), 403
1458
+
1459
+ # ุญุฐู ุงู„ู‚ุงุฆู…ุฉ
1460
+ db.session.delete(playlist)
1461
+ db.session.commit()
1462
+ return jsonify({'success': True})
1463
+
1464
+
1465
+
1466
+
1467
+ def get_current_user_id():
1468
+ # ุงูุชุฑุถ ุฃู†ูƒ ุชุณุชุฎุฏู… Flask-Login
1469
+ if current_user.is_authenticated:
1470
+ return current_user.id
1471
+ else:
1472
+ return None # ุฃูˆ ูŠู…ูƒู†ูƒ ุฑูุน ุงุณุชุซู†ุงุก ุฅุฐุง ู„ู… ูŠูƒู† ุงู„ู…ุณุชุฎุฏู… ู…ุนุชู…ุฏู‹ุง
1473
+
1474
+ @app.route('/get_playlists')
1475
+ def get_playlists():
1476
+ current_user_id = get_current_user_id()
1477
+
1478
+ if current_user_id is None:
1479
+ return jsonify({"error": "User not authenticated"}), 401
1480
+
1481
+ # ุฌู„ุจ ุงู„ู‚ูˆุงุฆู… ุงู„ุนุงู…ุฉ ุงู„ุชูŠ ู„ุง ุชู†ุชู…ูŠ ุฅู„ู‰ ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1482
+ playlists = Playlist.query.filter(
1483
+ Playlist.is_public == True, # ูู‚ุท ุงู„ู‚ูˆุงุฆู… ุงู„ุนุงู…ุฉ
1484
+ Playlist.user_id != current_user_id # ุงุณุชุจุนุงุฏ ู‚ูˆุงุฆู… ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1485
+ ).all()
1486
+
1487
+ # ุชุญูˆูŠู„ ุงู„ุจูŠุงู†ุงุช ุฅู„ู‰ ุชู†ุณูŠู‚ JSON
1488
+ playlists_data = [{'id': playlist.id, 'name': playlist.name} for playlist in playlists]
1489
+
1490
+ return jsonify(playlists_data)
1491
+
1492
+
1493
+ @app.route('/get_user_playlists')
1494
+ def get_user_playlists():
1495
+ # ุงู„ุญุตูˆู„ ุนู„ู‰ ู…ุนุฑู ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1496
+ current_user_id = get_current_user_id()
1497
+
1498
+ if current_user_id is None:
1499
+ return jsonify({"error": "User not authenticated"}), 401
1500
+
1501
+ # ุฌู„ุจ ู‚ูˆุงุฆู… ุงู„ุชุดุบูŠู„ ุงู„ุฎุงุตุฉ ุจุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1502
+ playlists = Playlist.query.filter(
1503
+ Playlist.user_id == current_user_id # ูู‚ุท ู‚ูˆุงุฆู… ุงู„ู…ุณุชุฎุฏู… ุงู„ุญุงู„ูŠ
1504
+ ).all()
1505
+
1506
+ # ุชุญูˆูŠู„ ุงู„ุจูŠุงู†ุงุช ุฅู„ู‰ ุชู†ุณูŠู‚ JSON
1507
+ playlists_data = [{'id': playlist.id, 'name': playlist.name} for playlist in playlists]
1508
+
1509
+ return jsonify(playlists_data)
1510
+
1511
+
1512
+ @app.route('/get_playlist_videos/<int:playlist_id>')
1513
+ @login_required
1514
+ def get_playlist_videos(playlist_id):
1515
+ # ุฌู„ุจ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„ ุจูˆุงุณุทุฉ ุงู„ู…ุนุฑูุŒ ุฅุฐุง ู„ู… ุชูˆุฌุฏ ุชุฑุฌุน 404
1516
+ playlist = Playlist.query.get_or_404(playlist_id)
1517
+
1518
+ # ุฌู„ุจ ุงู„ููŠุฏูŠูˆู‡ุงุช ุงู„ู…ุฑุชุจุทุฉ ุจู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„ ุงู„ู…ุญุฏุฏุฉ ุจุงุณุชุฎุฏุงู… ุฌุฏูˆู„ ุงู„ูˆุณูŠุท PlaylistVideo
1519
+ videos = db.session.query(Video).join(PlaylistVideo).filter(PlaylistVideo.playlist_id == playlist_id).all()
1520
+
1521
+ # ุชุญู‚ู‚ ุฅุฐุง ูƒุงู† ุงู„ู…ุณุชุฎุฏู… ู‡ูˆ ู…ุงู„ูƒ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„
1522
+ is_owner = playlist.user_id == current_user.id
1523
+
1524
+ # ุชุฌู‡ูŠุฒ ุงู„ุจูŠุงู†ุงุช ู„ุฅุฑุณุงู„ู‡ุง
1525
+ videos_data = [{
1526
+ 'id': video.id,
1527
+ 'title': video.title,
1528
+ 'thumbnail_path': video.thumbnail_path,
1529
+ 'uploader_name': video.uploader_name,
1530
+ 'uploader_image': video.uploader_image,
1531
+ 'upload_time': video.upload_time.strftime('%Y-%m-%d %H:%M:%S'), # ุชู†ุณูŠู‚ ุงู„ูˆู‚ุช
1532
+ 'is_owner': is_owner # ุฅุถุงูุฉ ุฅุฐุง ูƒุงู† ุงู„ู…ุณุชุฎุฏู… ู‡ูˆ ู…ุงู„ูƒ ุงู„ู‚ุงุฆู…ุฉ
1533
+ } for video in videos]
1534
+
1535
+ return jsonify(videos_data)
1536
+
1537
+
1538
+ @app.route('/get_video/<int:video_id>')
1539
+ def get_video(video_id):
1540
+ # ุฌู„ุจ ุงู„ููŠุฏูŠูˆ ุจูˆุงุณุทุฉ ุงู„ู…ุนุฑูุŒ ุฅุฐุง ู„ู… ูŠูƒู† ู…ูˆุฌูˆุฏู‹ุง ุชุฑุฌุน 404
1541
+ video = Video.query.get_or_404(video_id)
1542
+
1543
+ # ุชุฌู‡ูŠุฒ ุงู„ุจูŠุงู†ุงุช ุงู„ุฎุงุตุฉ ุจุงู„ููŠุฏูŠูˆ ู„ุฅุฑุณุงู„ู‡ุง
1544
+ video_data = {
1545
+ 'id': video.id,
1546
+ 'title': video.title,
1547
+ 'video_path': video.video_path,
1548
+ 'uploader_name': video.uploader_name,
1549
+ 'uploader_image': video.uploader_image,
1550
+ 'upload_time': video.upload_time.strftime('%Y-%m-%d %H:%M:%S') # ุชู†ุณูŠู‚ ุงู„ูˆู‚ุช
1551
+ }
1552
+
1553
+ return jsonify(video_data)
1554
+
1555
+
1556
+
1557
+ # ุญุฐู ุงู„ููŠุฏูŠูˆ ู…ู† ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„
1558
+ @app.route('/delete_video_from_playlist', methods=['POST'])
1559
+ @login_required
1560
+ def delete_video_from_playlist():
1561
+ video_id = request.json.get('video_id')
1562
+ playlist_id = request.json.get('playlist_id')
1563
+
1564
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ููŠุฏูŠูˆ ู…ูˆุฌูˆุฏ ููŠ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„
1565
+ playlist_video = PlaylistVideo.query.filter_by(video_id=video_id, playlist_id=playlist_id).first()
1566
+
1567
+ if not playlist_video:
1568
+ return jsonify({"message": "ุงู„ููŠุฏูŠูˆ ู„ูŠุณ ู…ูˆุฌูˆุฏู‹ุง ููŠ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„"}), 400
1569
+
1570
+ # ุงู„ุชุญู‚ู‚ ู…ู† ุฃู† ุงู„ู…ุณุชุฎุฏู… ู‡ูˆ ู…ู† ุฃู†ุดุฃ ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„
1571
+ playlist = Playlist.query.get(playlist_id)
1572
+ if playlist.user_id != current_user.id:
1573
+ return jsonify({"message": "ู„ูŠุณ ู„ุฏูŠูƒ ุตู„ุงุญูŠุฉ ู„ุญุฐู ุงู„ููŠุฏูŠูˆ ู…ู† ู‡ุฐู‡ ุงู„ู‚ุงุฆู…ุฉ"}), 403
1574
+
1575
+ # ุญุฐู ุงู„ุณุฌู„ ู…ู† ุฌุฏูˆู„ PlaylistVideo
1576
+ db.session.delete(playlist_video)
1577
+ db.session.commit()
1578
+
1579
+ return jsonify({"message": "ุชู… ุญุฐู ุงู„ููŠุฏูŠูˆ ู…ู† ู‚ุงุฆู…ุฉ ุงู„ุชุดุบูŠู„ ุจู†ุฌุงุญ"}), 200
1580
+
1581
+
1582
+ if __name__ == '__main__':
1583
+ with app.app_context():
1584
+ db.create_all()
1585
+ Session = scoped_session(sessionmaker(bind=db.engine))
1586
+ app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
1587
+
requirements.txt ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.4.4
2
+ aiohttp==3.11.11
3
+ aiosignal==1.3.2
4
+ attrs==25.1.0
5
+ blinker==1.9.0
6
+ Brotli==1.1.0
7
+ certifi==2024.12.14
8
+ charset-normalizer==3.4.1
9
+ click==8.1.8
10
+ colorama==0.4.6
11
+ Flask==3.1.0
12
+ Flask-Login==0.6.3
13
+ Flask-SQLAlchemy==3.1.1
14
+ frozenlist==1.5.0
15
+ g4f==0.4.3.4
16
+ greenlet==3.1.1
17
+ idna==3.10
18
+ imageio==2.37.0
19
+ itsdangerous==2.2.0
20
+ Jinja2==3.1.5
21
+ Markdown==3.7
22
+ MarkupSafe==3.0.2
23
+ multidict==6.1.0
24
+ nest-asyncio==1.6.0
25
+ numpy==2.2.2
26
+ opencv-python==4.11.0.86
27
+ pillow==11.1.0
28
+ propcache==0.2.1
29
+ pycryptodome==3.21.0
30
+ requests==2.32.3
31
+ SQLAlchemy==2.0.37
32
+ typing_extensions==4.12.2
33
+ urllib3==2.3.0
34
+ Werkzeug==3.1.3
35
+ yarl==1.18.3
36
+ aiohappyeyeballs==2.4.4
37
+ aiohttp==3.11.11
38
+ aiosignal==1.3.2
39
+ attrs==25.1.0
40
+ blinker==1.9.0
41
+ Brotli==1.1.0
42
+ certifi==2024.12.14
43
+ charset-normalizer==3.4.1
44
+ click==8.1.8
45
+ colorama==0.4.6
46
+ Flask==3.1.0
47
+ Flask-Login==0.6.3
48
+ Flask-SQLAlchemy==3.1.1
49
+ frozenlist==1.5.0
50
+ greenlet==3.1.1
51
+ idna==3.10
52
+ imageio==2.37.0
53
+ itsdangerous==2.2.0
54
+ Jinja2==3.1.5
55
+ Markdown==3.7
56
+ MarkupSafe==3.0.2
57
+ multidict==6.1.0
58
+ nest-asyncio==1.6.0
59
+ numpy==2.2.2
60
+ opencv-python==4.11.0.86
61
+ pillow==11.1.0
62
+ propcache==0.2.1
63
+ pycryptodome==3.21.0
64
+ requests==2.32.3
65
+ SQLAlchemy==2.0.37
66
+ typing_extensions==4.12.2
67
+ urllib3==2.3.0
68
+ Werkzeug==3.1.3
69
+ yarl==1.18.3
70
+ requests==2.32.3
71
+ uvicorn[standard]
72
+