From 918d6a98005696877ece658ef0faa7b8707587dd Mon Sep 17 00:00:00 2001 From: Olai Date: Thu, 13 Jun 2024 19:47:01 +0200 Subject: [PATCH] Added Images and Video support. Also improved the consistently of the database and the login section --- .gitignore | 5 +- app.py | 182 +++++++++++++++++++----------- static/styles.css | 185 +++++++++++++++++++++--------- templates/chat.html | 237 +++++++++++++++++++++++++++++++++++++++ templates/dashboard.html | 217 ++++++++++++----------------------- templates/sidebar.html | 16 +++ 6 files changed, 577 insertions(+), 265 deletions(-) create mode 100644 templates/chat.html create mode 100644 templates/sidebar.html diff --git a/.gitignore b/.gitignore index f1b7bb6..1ed4632 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.db -secret.key \ No newline at end of file +secret.key +app.log +/cdn +/uploads diff --git a/app.py b/app.py index 956ebe7..5eb0f19 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ -from flask import Flask, request, render_template, redirect, url_for, session, jsonify +from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory from flask_sqlalchemy import SQLAlchemy -from cryptography.fernet import Fernet +from cryptography.fernet import Fernet, InvalidToken import os from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash @@ -10,24 +10,21 @@ app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///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) -# Function to generate a new encryption key -def generate_key(): - key = Fernet.generate_key() - with open("secret.key", "wb") as key_file: - key_file.write(key) - -# Function to load the encryption key +# Load encryption key def load_key(): return open("secret.key", "rb").read() # Ensure the key file exists if not os.path.exists("secret.key"): - generate_key() + key = Fernet.generate_key() + with open("secret.key", "wb") as key_file: + key_file.write(key) -# Load the key key = load_key() cipher = Fernet(key) @@ -42,6 +39,7 @@ class Message(db.Model): 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.utcnow) class PendingMessage(db.Model): @@ -49,6 +47,7 @@ class PendingMessage(db.Model): 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.utcnow) class Friend(db.Model): @@ -83,7 +82,7 @@ def register(): db.session.add(new_user) db.session.commit() return redirect(url_for('login')) - except: + except Exception as e: return 'Username already exists!' return render_template('register.html') @@ -95,6 +94,7 @@ def login(): user = User.query.filter_by(username=username).first() if user and check_password_hash(user.password, password): session['username'] = user.username + session['user_id'] = user.id return redirect(url_for('dashboard')) return 'Invalid credentials' return render_template('login.html') @@ -111,59 +111,114 @@ def dashboard(): pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all() messages = Message.query.filter_by(receiver=session['username']).all() - decrypted_pending_messages = [(msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp) for msg in pending_messages] - decrypted_messages = [(msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp) for msg in messages] + 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)) - return render_template('dashboard.html', username=session['username'], friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages) + 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)) + + 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) + 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() + 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() + return render_template('chat.html', username=session['username'], friend_id=friend_id, friend_username=friend_user.username, friends=friend_users, friend_requests=friend_requests) + else: + return redirect(url_for('dashboard')) return redirect(url_for('login')) @app.route('/send_message/', methods=['POST']) def send_message(receiver): if 'username' in session: - data = request.get_json() - content = data['content'] - encrypted_content = cipher.encrypt(content.encode()).decode() + try: + content = request.form.get('content') + timestamp = request.form.get('timestamp') + file = request.files.get('image') or request.files.get('video') + content_type = 'text' - # Check if they are friends - user = User.query.filter_by(username=session['username']).first() - receiver_user = User.query.filter_by(username=receiver).first() - if not receiver_user: - return jsonify({'error': 'User not found'}), 404 - friend = Friend.query.filter_by(user_id=user.id, friend_id=receiver_user.id).first() + if file: + filename = f"{datetime.utcnow().timestamp()}_{file.filename}" + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + content = filename + content_type = 'file' - if friend: - new_message = Message(sender=session['username'], receiver=receiver, content=encrypted_content) - db.session.add(new_message) - db.session.commit() - decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() - socketio.emit('new_message', { - 'sender': session['username'], - 'content': decrypted_content, - 'timestamp': new_message.timestamp.strftime("%Y-%m-%d %H:%M:%S") - }, room=receiver) - else: - pending_message = PendingMessage(sender=session['username'], receiver=receiver, content=encrypted_content) - db.session.add(pending_message) - db.session.commit() + if content or file: + # Encrypt text content only, do not encrypt file names + encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content + timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S') - return jsonify({'status': 'Message sent'}), 200 + # Check if they are friends + user = User.query.filter_by(username=session['username']).first() + receiver_user = User.query.filter_by(id=receiver).first() + if not receiver_user: + return jsonify({'error': 'User not found'}), 404 + friend = Friend.query.filter_by(user_id=user.id, friend_id=receiver_user.id).first() + + if friend: + new_message = Message(sender=session['username'], receiver=receiver_user.username, 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'], + 'content': decrypted_content, + 'content_type': content_type, + 'timestamp': timestamp + }, room=receiver_user.username) + 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() + + return jsonify({'status': 'Message sent'}), 200 + return jsonify({'error': 'No content or file provided'}), 400 + except Exception as e: + return jsonify({'error': str(e)}), 500 return jsonify({'error': 'Unauthorized'}), 401 -@app.route('/get_messages/', methods=['GET']) -def get_messages(friend_username): +@app.route('/get_messages/', methods=['GET']) +def get_messages(friend_id): if 'username' in session: - current_user = session['username'] - messages = Message.query.filter( - ((Message.sender == current_user) & (Message.receiver == friend_username)) | - ((Message.sender == friend_username) & (Message.receiver == current_user)) - ).all() - decrypted_messages = [ - {'sender': msg.sender, 'content': cipher.decrypt(msg.content.encode()).decode(), 'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S")} - for msg in messages - ] - return jsonify({'messages': decrypted_messages}) + 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 == current_user.username) & (Message.receiver == friend_user.username)) | + ((Message.sender == friend_user.username) & (Message.receiver == current_user.username)) + ).all() + decrypted_messages = [ + {'sender': msg.sender, '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 + ] + return jsonify({'messages': decrypted_messages}) return jsonify({'error': 'Unauthorized'}), 401 +@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: @@ -202,7 +257,7 @@ def accept_friend(request_id): # Move pending messages to messages 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, timestamp=pm.timestamp) + 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() @@ -257,18 +312,9 @@ def remove_friend(friend_id): @app.route('/logout') def logout(): session.pop('username', None) + session.pop('user_id', None) return redirect(url_for('login')) -@socketio.on('join') -def handle_join(data): - username = data['username'] - join_room(username) - -@socketio.on('leave') -def handle_leave(data): - username = data['username'] - leave_room(username) - @socketio.on('connect') def handle_connect(): if 'username' in session: @@ -287,7 +333,17 @@ def handle_disconnect(): db.session.commit() emit('user_offline', {'username': user.username}, broadcast=True) +@socketio.on('join') +def handle_join(data): + username = data['username'] + join_room(username) + +@socketio.on('leave') +def handle_leave(data): + username = data['username'] + leave_room(username) + if __name__ == '__main__': with app.app_context(): db.create_all() - socketio.run(app, debug=True) + socketio.run(app, host='0.0.0.0', port=8086, debug=True) diff --git a/static/styles.css b/static/styles.css index db39e12..81e184b 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,8 +1,11 @@ +/* styles.css */ + body { display: flex; height: 100vh; margin: 0; } + .sidebar { width: 250px; background-color: #2f3136; @@ -10,25 +13,151 @@ body { padding: 20px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); } + .sidebar h3 { margin-top: 0; } + .sidebar ul { list-style: none; padding: 0; } + .sidebar ul li { margin: 10px 0; cursor: pointer; display: flex; align-items: center; } + .sidebar ul li .status-indicator { width: 10px; height: 10px; border-radius: 50%; margin-right: 10px; } + +.content { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.friend-request { + margin-bottom: 20px; +} + +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + padding: 20px; + overflow-y: auto; +} + +.chat-header { + background-color: #36393f; + color: white; + padding: 10px; + margin-bottom: 20px; +} + +.messages { + flex: 1; + overflow-y: auto; + margin-bottom: 20px; +} + +.message { + margin-bottom: 10px; + position: relative; +} + +.message strong { + color: #7289da; +} + +.message .timestamp { + display: none; + position: absolute; + top: -20px; + right: 0; + background-color: rgba(0,0,0,0.7); + color: white; + padding: 5px; + border-radius: 3px; +} + +.message:hover .timestamp { + display: block; +} + +.send-message-form { + display: flex; + align-items: center; +} + +.send-message-form input[type="text"] { + flex: 1; + padding: 10px; + margin-right: 10px; + border: 1px solid #ccc; + border-radius: 5px; +} + +.send-message-form button { + padding: 10px; + border: none; + background-color: #7289da; + color: white; + border-radius: 5px; + cursor: pointer; +} + +.image-preview { + position: relative; + display: inline-block; +} + +.image-preview img { + max-width: 100px; + max-height: 100px; + display: block; +} + +.image-preview .remove-image { + position: absolute; + top: 0; + right: 0; + background-color: rgba(0, 0, 0, 0.5); + color: white; + border: none; + border-radius: 50%; + cursor: pointer; +} + +/* Image enhancer */ +.image-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + justify-content: center; + align-items: center; + z-index: 1000; +} + +.image-overlay img { + max-width: 90%; + max-height: 90%; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.5); + border-radius: 10px; + cursor: pointer; +} + .online{ margin-right: 10px; height: 10px; @@ -42,60 +171,4 @@ body { width: 10px; background-color: #80848e; border-radius: 50%; -} -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - padding: 20px; - overflow-y: auto; -} -.chat-header { - background-color: #36393f; - color: white; - padding: 10px; - margin-bottom: 20px; -} -.messages { - flex: 1; - overflow-y: auto; - margin-bottom: 20px; -} -.message { - margin-bottom: 10px; - position: relative; -} -.message strong { - color: #7289da; -} -.message .timestamp { - display: none; - position: absolute; - top: -20px; - right: 0; - background-color: rgba(0,0,0,0.7); - color: white; - padding: 5px; - border-radius: 3px; -} -.message:hover .timestamp { - display: block; -} -.send-message-form { - display: flex; -} -.send-message-form input { - flex: 1; - padding: 10px; - margin-right: 10px; - border: 1px solid #ccc; - border-radius: 5px; -} -.send-message-form button { - padding: 10px; - border: none; - background-color: #7289da; - color: white; - border-radius: 5px; - cursor: pointer; } \ No newline at end of file diff --git a/templates/chat.html b/templates/chat.html new file mode 100644 index 0000000..344df7a --- /dev/null +++ b/templates/chat.html @@ -0,0 +1,237 @@ + + + + + Chat + + + + + + + {% include 'sidebar.html' %} + +
+
+

Chatting with {{ friend_username }}

+
+
+ +
+
+ + + + + + + + +
+ +
+ Enhanced Image +
+
+ + diff --git a/templates/dashboard.html b/templates/dashboard.html index 7539f5a..128e05d 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -5,182 +5,109 @@ Dashboard - - -