from PIL import Image from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response from flask_sqlalchemy import SQLAlchemy from cryptography.fernet import Fernet, InvalidToken import hashlib import os import random import string import json from datetime import datetime, timedelta, timezone from werkzeug.utils import secure_filename from flask_socketio import SocketIO, emit, join_room, leave_room import logging import jwt from flask_migrate import Migrate # Set up logging logging.basicConfig(filename='app.log', level=logging.DEBUG, format='%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]', filemode='w') logger = logging.getLogger(__name__) app = Flask(__name__) app.config['SECRET_KEY'] = None # Initially set to None def load_secret_key(): secret_key_path = 'secret_key.json' if not os.path.exists(secret_key_path): secret_key = os.urandom(24) with open(secret_key_path, 'w') as key_file: json.dump({'SECRET_KEY': secret_key.hex()}, key_file) else: with open(secret_key_path, 'r') as key_file: secret_key = bytes.fromhex(json.load(key_file)['SECRET_KEY']) return secret_key app.config['SECRET_KEY'] = load_secret_key() app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///W:/site.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['UPLOAD_FOLDER'] = 'cdn' os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) db = SQLAlchemy(app) socketio = SocketIO(app) migrate = Migrate(app, db) # Load encryption key def load_key(): return open(r"W:\secret.key", "rb").read() # Ensure the key file exists if not os.path.exists(r"W:\secret.key"): key = Fernet.generate_key() with open(r"secret.key", "wb") as key_file: key_file.write(key) key = load_key() cipher = Fernet(key) # JWT Utility Functions def generate_token(user_id): payload = { 'user_id': user_id, 'exp': datetime.now(timezone.utc) + timedelta(days=7) } token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256') return token def decode_token(token): try: payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256']) return payload['user_id'] except jwt.ExpiredSignatureError: return None except jwt.InvalidTokenError: return None class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) password = db.Column(db.String(150), nullable=False) online = db.Column(db.Boolean, nullable=False, default=False) is_admin = db.Column(db.Boolean, nullable=False, default=False) disabled = db.Column(db.Boolean, nullable=False, default=False) profile_picture = db.Column(db.String(150), nullable=True) custom_css = db.Column(db.Text, nullable=True) class Message(db.Model): id = db.Column(db.Integer, primary_key=True) sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) receiver_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) content = db.Column(db.Text, nullable=False) content_type = db.Column(db.String(20), nullable=False, default='text') timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now(timezone.utc)) sender = db.relationship('User', foreign_keys=[sender_id]) receiver = db.relationship('User', foreign_keys=[receiver_id]) class PendingMessage(db.Model): id = db.Column(db.Integer, primary_key=True) sender = db.Column(db.String(150), nullable=False) receiver = db.Column(db.String(150), nullable=False) content = db.Column(db.Text, nullable=False) content_type = db.Column(db.String(20), nullable=False, default='text') timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now(timezone.utc)) class Friend(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) friend_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship('User', foreign_keys=[user_id]) friend = db.relationship('User', foreign_keys=[friend_id]) class FriendRequest(db.Model): id = db.Column(db.Integer, primary_key=True) sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) receiver_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) status = db.Column(db.String(20), nullable=False, default='pending') sender = db.relationship('User', foreign_keys=[sender_id]) receiver = db.relationship('User', foreign_keys=[receiver_id]) class Group(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(150), unique=True, nullable=False) admin_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) admin = db.relationship('User', foreign_keys=[admin_id]) class GroupMember(db.Model): id = db.Column(db.Integer, primary_key=True) group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship('User', foreign_keys=[user_id]) group = db.relationship('Group', foreign_keys=[group_id]) class GroupMessage(db.Model): id = db.Column(db.Integer, primary_key=True) group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) content = db.Column(db.Text, nullable=False) content_type = db.Column(db.String(20), nullable=False, default='text') timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now(timezone.utc)) group = db.relationship('Group', foreign_keys=[group_id]) sender = db.relationship('User', foreign_keys=[sender_id]) def generate_salt(): return os.urandom(16).hex() def sha256_password_hash(password, salt=None): if salt is None: salt = generate_salt() hash = hashlib.sha256((salt + password).encode()).hexdigest() return f"{salt}${hash}" @app.route('/') def index(): if 'user_id' in session: user_id = session['user_id'] user = User.query.get(user_id) if user: return redirect(url_for('dashboard')) return redirect(url_for('login')) @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form['username'].lower() password = request.form['password'] hashed_password = sha256_password_hash(password) profile_picture = "default_profile_picture.png" # Set default profile picture new_user = User(username=username, password=hashed_password, profile_picture=profile_picture) try: db.session.add(new_user) db.session.commit() logger.info(f"New user registered: {username}") # Automatically log the user in after registration session['username'] = new_user.username session['user_id'] = new_user.id session['is_admin'] = new_user.is_admin token = generate_token(new_user.id) response = make_response(redirect(url_for('dashboard'))) expires = datetime.now(timezone.utc) + timedelta(days=7) response.set_cookie('token', token, httponly=True, expires=expires) logger.info(f"User logged in: {username} after registration") return response except Exception as e: logger.error(f"Error registering user {username}: {e}") return 'Username already exists!' return render_template('register.html') @app.route('/login', methods=['GET', 'POST']) def login(): if 'username' in session: return redirect(url_for('dashboard')) if request.method == 'POST': username = request.form['username'].lower() password = request.form['password'] user = User.query.filter_by(username=username).first() if user: salt, stored_hash = user.password.split('$') if user.disabled: logger.warning(f"Disabled account login attempt: {username}") return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'}) if stored_hash == hashlib.sha256((salt + password).encode()).hexdigest(): session['username'] = user.username session['user_id'] = user.id session['is_admin'] = user.is_admin token = generate_token(user.id) response = make_response(jsonify({'status': 'success'})) expires = datetime.now(timezone.utc) + timedelta(days=7) response.set_cookie('token', token, httponly=True, expires=expires) logger.info(f"User logged in: {username}") return response logger.warning(f"Invalid login attempt for user: {username}") return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'}) return render_template('login.html') @app.route('/change_password', methods=['GET', 'POST']) def change_password(): if request.method == 'POST': username = request.form['username'].lower() current_password = request.form['current_password'] new_password = request.form['new_password'] user = User.query.filter_by(username=username).first() if user: current_salt, current_hash = user.password.split('$') if current_hash == hashlib.sha256((current_salt + current_password).encode()).hexdigest(): new_hashed_password = sha256_password_hash(new_password) user.password = new_hashed_password db.session.commit() logger.info(f"Password changed for user: {username}") return jsonify({'status': 'Password changed successfully'}) logger.warning(f"Password change failed for user: {username}") return jsonify({'status': 'error'}) return render_template('change_password.html') @app.route('/admin', methods=['GET']) def admin_panel(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if user and user.is_admin: users = User.query.all() return render_template('admin_panel.html', users=users) return redirect(url_for('login')) @app.route('/admin/change_password', methods=['POST']) def admin_change_password(): if 'username' in session: admin_user = User.query.filter_by(username=session['username']).first() if admin_user and admin_user.is_admin: try: data = request.get_json() user_id = data['user_id'] new_password = data['new_password'] user = User.query.get(user_id) if user: user.password = sha256_password_hash(new_password) db.session.commit() return jsonify({'status': 'success'}) return jsonify({'status': 'user not found'}), 404 except Exception as e: logger.error(f"Error changing password: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error'}), 401 @app.route('/admin/disable_account', methods=['POST']) def admin_disable_account(): if 'username' in session and session.get('is_admin'): try: data = request.get_json() user_id = data['user_id'] user = User.query.get(user_id) if user: user.disabled = True db.session.commit() return jsonify({'status': 'success'}) return jsonify({'status': 'user not found'}), 404 except Exception as e: logger.error(f"Error disabling account: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error'}), 401 @app.route('/admin/enable_account', methods=['POST']) def admin_enable_account(): if 'username' in session and session.get('is_admin'): try: data = request.get_json() user_id = data['user_id'] user = User.query.get(user_id) if user: user.disabled = False db.session.commit() return jsonify({'status': 'success'}) return jsonify({'status': 'user not found'}), 404 except Exception as e: logger.error(f"Error enabling account: {e}") return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error'}), 401 @app.route('/dashboard') def dashboard(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() friends = Friend.query.filter_by(user_id=user.id).all() friend_ids = [friend.friend_id for friend in friends] friend_users = User.query.filter(User.id.in_(friend_ids)).all() friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all() messages = Message.query.filter_by(receiver_id=user.id).all() group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() decrypted_pending_messages = [] for msg in pending_messages: try: decrypted_pending_messages.append((msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp)) except InvalidToken: decrypted_pending_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp)) decrypted_messages = [] for msg in messages: try: decrypted_messages.append((msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp)) except InvalidToken: decrypted_messages.append((msg.sender, "Invalid encrypted message", msg.timestamp)) profile_picture = user.profile_picture if user.profile_picture else 'default_profile_picture.png' for friend in friend_users: friend.profile_picture = friend.profile_picture if friend.profile_picture else 'default_profile_picture.png' logger.info(f"User {session['username']} accessed dashboard") return render_template('dashboard.html', username=session['username'], user_id=user.id, friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages, groups=groups, is_admin=user.is_admin, profile_picture=profile_picture) return redirect(url_for('login')) @app.route('/chat/') def chat(friend_id): if 'username' in session: current_user_id = session['user_id'] friend = Friend.query.filter_by(user_id=current_user_id, friend_id=friend_id).first() if friend: friend_user = User.query.filter_by(id=friend_id).first() user = User.query.filter_by(id=current_user_id).first() friends = Friend.query.filter_by(user_id=user.id).all() profile_picture = user.profile_picture friend_ids = [f.friend_id for f in friends] friend_users = User.query.filter(User.id.in_(friend_ids)).all() friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() logger.info(f"User {session['username']} opened chat with {friend_user.username}") return render_template('chat.html', friend=friend, profile_picture=profile_picture, username=session['username'], friend_id=friend_id, friend_username=friend_user.username, friends=friend_users, friend_requests=friend_requests, groups=groups, is_admin=user.is_admin) else: logger.warning(f"User {session['username']} attempted to access chat with non-friend user_id: {friend_id}") return redirect(url_for('dashboard')) return redirect(url_for('login')) @app.route('/send_message/', methods=['POST']) def send_message(receiver): if 'username' in session: try: content = request.form.get('content') timestamp = request.form.get('timestamp') file = request.files.get('file') content_type = 'text' if file: filename = f"{datetime.now(timezone.utc).timestamp()}_{file.filename}" file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) content = filename content_type = 'file' if content: encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) sender_user = User.query.filter_by(username=session['username']).first() receiver_user = User.query.filter_by(id=receiver).first() if not receiver_user: logger.error(f"Message send failed: User not found {receiver}") return jsonify({'error': 'User not found'}), 404 friend = Friend.query.filter_by(user_id=sender_user.id, friend_id=receiver_user.id).first() if friend: new_message = Message(sender_id=sender_user.id, receiver_id=receiver_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) db.session.add(new_message) db.session.commit() decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content socketio.emit('new_message', { 'sender': session['username'], 'receiver': receiver_user.username, 'content': decrypted_content, 'sender_profile_picture': sender_user.profile_picture, 'content_type': content_type, 'timestamp': timestamp, 'id': new_message.id }, room=receiver_user.username) logger.info(f"Message sent from {session['username']} to {receiver_user.username}") return jsonify({'status': 'Message sent', 'message_id': new_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200 else: pending_message = PendingMessage(sender=session['username'], receiver=receiver_user.username, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) db.session.add(pending_message) db.session.commit() logger.info(f"Pending message from {session['username']} to {receiver_user.username}") return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200 return jsonify({'error': 'No content or file provided'}), 400 except Exception as e: logger.error(f"Error sending message from {session['username']} to {receiver}: {e}") return jsonify({'error': str(e)}), 500 return jsonify({'error': 'Unauthorized'}), 401 @app.route('/get_messages/', methods=['GET']) def get_messages(friend_id): if 'username' in session: current_user_id = session['user_id'] current_user = User.query.filter_by(id=current_user_id).first() friend_user = User.query.filter_by(id=friend_id).first() if friend_user: friend = Friend.query.filter_by(user_id=current_user_id, friend_id=friend_id).first() if friend: messages = Message.query.filter( ((Message.sender_id == current_user.id) & (Message.receiver_id == friend_user.id)) | ((Message.sender_id == friend_user.id) & (Message.receiver_id == current_user.id)) ).all() decrypted_messages = [ { 'id': msg.id, 'sender': msg.sender.username, 'sender_profile_picture': f'{msg.sender.profile_picture}' if msg.sender.profile_picture else None, 'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content, 'content_type': msg.content_type, 'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S") } for msg in messages ] logger.info(f"Messages retrieved for chat between {current_user.username} and {friend_user.username}") return jsonify({'messages': decrypted_messages}) logger.warning(f"Unauthorized message retrieval attempt by user {session.get('username', 'unknown')}") return jsonify({'error': 'Unauthorized'}), 401 def generate_random_string(length=10): characters = string.ascii_letters + string.digits return ''.join(random.choice(characters) for i in range(length)) def allowed_file(filename): ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def compress_image(image_path, quality=15): with Image.open(image_path) as img: if img.format == 'PNG': img.save(image_path, "PNG", optimize=True) else: img = img.convert("RGB") img.save(image_path, "JPEG", quality=quality) @app.route('/upload_profile_picture', methods=['POST']) def upload_profile_picture(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if 'profile_picture' in request.files: file = request.files['profile_picture'] if file and allowed_file(file.filename): filename = secure_filename(file.filename) random_string = generate_random_string() filename_parts = filename.rsplit('.', 1) filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}" file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) compress_image(file_path, quality=85) user.profile_picture = filename db.session.commit() logger.info(f"User {session['username']} uploaded a new profile picture: {filename}") return redirect(url_for('dashboard')) return redirect(url_for('dashboard')) @app.route('/cdn/') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename) @app.route('/add_friend', methods=['POST']) def add_friend(): if 'username' in session: friend_username = request.form['friend_username'].lower() user = User.query.filter_by(username=session['username']).first() friend = User.query.filter_by(username=friend_username).first() if friend and user != friend: existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first() if existing_request: return jsonify({'status': 'Friend request already sent'}) existing_friend = Friend.query.filter( ((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) | ((Friend.user_id == friend.id) & (Friend.friend_id == user.id)) ).first() if existing_friend: return jsonify({'status': 'Already friends'}) friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id) db.session.add(friend_request) db.session.commit() socketio.emit('friend_request', { 'sender': session['username'], 'receiver': friend_username }, room=friend_username) logger.info(f"Friend request sent from {session['username']} to {friend_username}") return jsonify({'status': 'Friend request sent'}) logger.warning(f"Friend request failed from {session['username']} to {friend_username}: Friend not found or cannot add yourself as a friend") return jsonify({'status': 'Friend not found or cannot add yourself as a friend'}) return jsonify({'status': 'Unauthorized'}), 401 @app.route('/accept_friend/', methods=['POST']) def accept_friend(request_id): if 'username' in session: friend_request = FriendRequest.query.get(request_id) if friend_request and friend_request.receiver.username == session['username']: friend_request.status = 'accepted' db.session.commit() user_id = friend_request.receiver_id friend_id = friend_request.sender_id new_friend_1 = Friend(user_id=user_id, friend_id=friend_id) new_friend_2 = Friend(user_id=friend_id, friend_id=user_id) db.session.add(new_friend_1) db.session.add(new_friend_2) db.session.commit() pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all() for pm in pending_messages: new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, content_type=pm.content_type, timestamp=pm.timestamp) db.session.add(new_message) db.session.delete(pm) db.session.commit() socketio.emit('friend_request_accepted', { 'sender': friend_request.sender.username, 'receiver': friend_request.receiver.username }, room=friend_request.sender.username) logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}") return redirect(url_for('dashboard')) logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}") return 'Friend request not found' return redirect(url_for('login')) @app.route('/reject_friend/', methods=['POST']) def reject_friend(request_id): if 'username' in session: friend_request = FriendRequest.query.get(request_id) if friend_request and friend_request.receiver.username == session['username']: db.session.delete(friend_request) db.session.commit() socketio.emit('friend_request_rejected', { 'sender': friend_request.sender.username, 'receiver': friend_request.receiver.username }, room=friend_request.sender.username) logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}") return redirect(url_for('dashboard')) logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}") return 'Friend request not found' return redirect(url_for('login')) @app.route('/remove_friend/', methods=['POST']) def remove_friend(friend_id): if 'username' in session: user = User.query.filter_by(username=session['username']).first() friend = Friend.query.filter_by(user_id=user.id, friend_id=friend_id).first() if friend: db.session.delete(friend) reciprocal_friend = Friend.query.filter_by(user_id=friend.friend_id, friend_id=user.id).first() if reciprocal_friend: db.session.delete(reciprocal_friend) sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first() if sent_request: db.session.delete(sent_request) received_request = FriendRequest.query.filter_by(sender_id=friend_id, receiver_id=user.id).first() if received_request: db.session.delete(received_request) db.session.commit() socketio.emit('friend_removed', { 'sender': session['username'], 'receiver': friend.friend.username }, room=friend.friend.username) logger.info(f"Friend {friend.friend.username} removed by {session['username']}") return jsonify({'status': 'success'}), 200 logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}") return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404 return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401 @app.route('/create_group', methods=['POST']) def create_group(): if 'username' in session: try: user = User.query.filter_by(username=session['username']).first() group_name = request.form['group_name'] member_usernames = [username.strip() for username in request.form['members'].split(',')] existing_group = Group.query.filter_by(name=group_name).first() if existing_group: return jsonify({'status': 'Error', 'message': 'A group with this name already exists.'}), 400 for username in member_usernames: member = User.query.filter_by(username=username).first() if not member: return jsonify({'status': 'Error', 'message': f'User {username} does not exist.'}), 400 friendship = Friend.query.filter( ((Friend.user_id == user.id) & (Friend.friend_id == member.id)) | ((Friend.user_id == member.id) & (Friend.friend_id == user.id)) ).first() if not friendship: return jsonify({'status': 'Error', 'message': f'User {username} is not your friend.'}), 400 new_group = Group(name=group_name, admin_id=user.id) db.session.add(new_group) db.session.flush() new_group_member = GroupMember(group_id=new_group.id, user_id=user.id) db.session.add(new_group_member) for username in member_usernames: member = User.query.filter_by(username=username).first() if member: new_group_member = GroupMember(group_id=new_group.id, user_id=member.id) db.session.add(new_group_member) db.session.commit() return jsonify({'status': 'Group created'}) except Exception as e: db.session.rollback() return jsonify({'status': 'Error', 'message': str(e)}), 500 return jsonify({'status': 'Unauthorized'}), 401 @app.route('/invite_to_group', methods=['POST']) def invite_to_group(): if 'username' in session: group_id = request.form['group_id'] friend_username = request.form['friend_username'] group = Group.query.filter_by(id=group_id).first() if group and group.admin.username == session['username']: friend = User.query.filter_by(username=friend_username).first() if friend: existing_member = GroupMember.query.filter_by(group_id=group.id, user_id=friend.id).first() if not existing_member: group_member = GroupMember(group_id=group.id, user_id=friend.id) db.session.add(group_member) db.session.commit() return jsonify({'status': 'User added to group'}) return jsonify({'status': 'User already in group'}) return jsonify({'status': 'User not found'}) return jsonify({'status': 'Unauthorized'}) return jsonify({'status': 'Unauthorized'}), 401 @app.route('/group_chat/') def group_chat(group_id): if 'username' in session: group = db.session.get(Group, group_id) user = User.query.filter_by(username=session['username']).first() friends = Friend.query.filter_by(user_id=user.id).all() friend_ids = [friend.friend_id for friend in friends] friend_users = User.query.filter(User.id.in_(friend_ids)).all() group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() if group: members = GroupMember.query.filter_by(group_id=group_id).all() member_ids = [member.user_id for member in members] member_users = User.query.filter(User.id.in_(member_ids)).all() logger.info(f"User {session['username']} accessed group chat: {group.name}") return render_template('group_chat.html', username=session['username'], group=group, members=member_users, friends=friend_users, groups=groups, is_admin=user.is_admin) logger.warning(f"Group not found with id: {group_id}") return redirect(url_for('dashboard')) return redirect(url_for('login')) @app.route('/send_group_message/', methods=['POST']) def send_group_message(group_id): if 'username' in session: try: content = request.form.get('content') timestamp = request.form.get('timestamp') file = request.files.get('file') content_type = 'text' if file: filename = f"{datetime.now(timezone.utc).timestamp()}_{file.filename}" file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) content = filename content_type = 'file' if content: encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) sender_user = User.query.filter_by(username=session['username']).first() if not sender_user: return jsonify({'error': 'User not found'}), 404 new_message = GroupMessage(group_id=group_id, sender_id=sender_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) db.session.add(new_message) db.session.commit() decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content socketio.emit('new_group_message', { 'group_id': group_id, 'sender': session['username'], 'content': decrypted_content, 'sender_profile_picture': sender_user.profile_picture, 'content_type': content_type, 'timestamp': timestamp, 'id': new_message.id }, room=f"group_{group_id}") logger.info(f"Group message sent from {session['username']} to group {group_id}") return jsonify({'status': 'Message sent', 'message_id': new_message.id}), 200 return jsonify({'error': 'No content or file provided'}), 400 except Exception as e: logger.error(f"Error sending group message from {session['username']} to group {group_id}: {e}") return jsonify({'error': 'Internal server error'}), 500 return jsonify({'error': 'Unauthorized'}), 401 @app.route('/get_group_messages/', methods=['GET']) def get_group_messages(group_id): if 'username' in session: group = db.session.get(Group, group_id) if group: messages = db.session.query(GroupMessage, User).join(User, GroupMessage.sender_id == User.id).filter(GroupMessage.group_id == group_id).all() message_list = [ { 'id': msg.GroupMessage.id, 'group_id': msg.GroupMessage.group_id, 'sender': msg.User.username, 'sender_profile_picture': msg.User.profile_picture, 'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content, 'content_type': msg.GroupMessage.content_type, 'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S") } for msg in messages ] logger.info(f"Messages retrieved for group {group_id}") return jsonify({'messages': message_list}) else: logger.warning(f"Group not found with id: {group_id}") return jsonify({'error': 'Group not found'}), 404 logger.warning(f"Unauthorized message retrieval attempt by user {session.get('username', 'unknown')}") return jsonify({'error': 'Unauthorized'}), 401 @app.route('/get_groups', methods=['GET']) def get_groups(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if user: group_memberships = GroupMember.query.filter_by(user_id=user.id).all() group_ids = [membership.group_id for membership in group_memberships] groups = Group.query.filter(Group.id.in_(group_ids)).all() group_list = [{'id': group.id, 'name': group.name} for group in groups] return jsonify({'groups': group_list}) return jsonify({'error': 'Unauthorized'}), 401 @app.route('/settings', methods=['GET', 'POST']) def settings(): if 'username' in session: if request.method == 'POST': theme = request.form['theme'] session['theme'] = theme return redirect(url_for('settings')) current_theme = session.get('theme', 'automatic') return render_template('settings.html', current_theme=current_theme) return redirect(url_for('login')) @app.route('/custom_css', methods=['GET', 'POST']) def custom_css(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if request.method == 'POST': custom_css = request.form.get('custom_css') user.custom_css = custom_css db.session.commit() return redirect(url_for('dashboard')) return render_template('custom_css.html', custom_css=user.custom_css if user.custom_css else "") return redirect(url_for('login')) @app.context_processor def inject_user(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() return dict(current_user=user) return dict(current_user=None) @app.route('/logout') def logout(): session.pop('username', None) session.pop('user_id', None) response = make_response(redirect(url_for('login'))) response.set_cookie('token', '', expires=0) logger.info(f"User logged out") return response @socketio.on('ping') def handle_ping(): emit('pong') @socketio.on('connect') def handle_connect(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if user: user.online = True user.last_activity = datetime.now(timezone.utc) db.session.commit() emit('user_online', {'username': user.username}, broadcast=True) logger.info(f"User {session['username']} connected") @socketio.on('disconnect') def handle_disconnect(): if 'username' in session: user = User.query.filter_by(username=session['username']).first() if user: user.online = False db.session.commit() emit('user_offline', {'username': user.username}, broadcast=True) logger.info(f"User {session['username']} disconnected") @socketio.on('send_message') def handle_send_message(data): sender = session['username'] receiver_id = data['receiver_id'] message = data['message'] timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Save the message in the database new_message = Message(sender_id=User.query.filter_by(username=sender).first().id, receiver_id=receiver_id, content=message, timestamp=timestamp) db.session.add(new_message) db.session.commit() # Emit the message to the receiver if they are online and not in the same chat receiver = User.query.get(receiver_id) if receiver.online: emit('new_message', { 'sender': sender, 'message': message, 'timestamp': timestamp, 'receiver_id': receiver_id }, room=receiver.username) @socketio.on('send_group_message') def handle_send_group_message(data): sender = session['username'] group_id = data['group_id'] message = data['message'] timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Save the message in the database new_group_message = GroupMessage(sender_id=User.query.filter_by(username=sender).first().id, group_id=group_id, content=message, timestamp=timestamp) db.session.add(new_group_message) db.session.commit() # Emit the message to the group if they are online and not in the same group chat group_members = GroupMember.query.filter_by(group_id=group_id).all() for member in group_members: user = User.query.get(member.user_id) if user.username != sender and user.online: emit('new_group_message', { 'sender': sender, 'message': message, 'timestamp': timestamp, 'group_id': group_id }, room=user.username) @socketio.on('join') def on_join(data): username = data['username'] join_room(username) logger.info(f"User {username} joined room {username}") @socketio.on('leave') def on_leave(data): username = data['username'] leave_room(username) logger.info(f"User {username} left room {username}") @socketio.on('join_group') def handle_join_group(data): group_id = data['group_id'] username = session['username'] join_room(f"group_{group_id}") logger.info(f"User {username} joined group room {group_id}") @socketio.on('leave_group') def handle_leave_group(data): group_id = data['group_id'] username = session['username'] leave_room(f"group_{group_id}") logger.info(f"User {username} left group room {group_id}") if __name__ == '__main__': with app.app_context(): db.create_all() socketio.run(app, host='0.0.0.0', port=8086, debug=True, allow_unsafe_werkzeug=True)