Updated online detection and fixed bugs

Updated online detection and fixed bugs
This commit is contained in:
Olai Vike Bøe 2024-06-25 23:27:34 +02:00
parent 0ac1f2795b
commit bc9fcda6aa
17 changed files with 342 additions and 169 deletions

209
app.py
View file

@ -1,13 +1,13 @@
from PIL import Image # Import the Image module from Pillow from PIL import Image
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from cryptography.fernet import Fernet, InvalidToken from cryptography.fernet import Fernet, InvalidToken
import hashlib
import os import os
import random import random
import string import string
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from werkzeug.utils import secure_filename # Add this line from werkzeug.utils import secure_filename
from werkzeug.security import generate_password_hash, check_password_hash
from flask_socketio import SocketIO, emit, join_room, leave_room from flask_socketio import SocketIO, emit, join_room, leave_room
import logging import logging
import jwt import jwt
@ -67,6 +67,7 @@ class User(db.Model):
is_admin = 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) disabled = db.Column(db.Boolean, nullable=False, default=False)
profile_picture = db.Column(db.String(150), nullable=True) profile_picture = db.Column(db.String(150), nullable=True)
custom_css = db.Column(db.Text, nullable=True)
class Message(db.Model): class Message(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -124,18 +125,22 @@ class GroupMessage(db.Model):
group = db.relationship('Group', foreign_keys=[group_id]) group = db.relationship('Group', foreign_keys=[group_id])
sender = db.relationship('User', foreign_keys=[sender_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('/') @app.route('/')
def index(): def index():
token = request.cookies.get('token') if 'user_id' in session:
if token: user_id = session['user_id']
user_id = decode_token(token) user = User.query.get(user_id)
if user_id: if user:
user = User.query.get(user_id) return redirect(url_for('dashboard'))
if user:
session['username'] = user.username
session['user_id'] = user.id
return redirect(url_for('dashboard'))
return redirect(url_for('login')) return redirect(url_for('login'))
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
@ -143,7 +148,7 @@ def register():
if request.method == 'POST': if request.method == 'POST':
username = request.form['username'].lower() username = request.form['username'].lower()
password = request.form['password'] password = request.form['password']
hashed_password = generate_password_hash(password) # Default method hashed_password = sha256_password_hash(password)
new_user = User(username=username, password=hashed_password) new_user = User(username=username, password=hashed_password)
try: try:
db.session.add(new_user) db.session.add(new_user)
@ -165,10 +170,11 @@ def login():
password = request.form['password'] password = request.form['password']
user = User.query.filter_by(username=username).first() user = User.query.filter_by(username=username).first()
if user: if user:
salt, stored_hash = user.password.split('$')
if user.disabled: if user.disabled:
logger.warning(f"Disabled account login attempt: {username}") logger.warning(f"Disabled account login attempt: {username}")
return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'}) return jsonify({'status': 'error', 'message': 'Account Disabled. Contact Support'})
if check_password_hash(user.password, password): if stored_hash == hashlib.sha256((salt + password).encode()).hexdigest():
session['username'] = user.username session['username'] = user.username
session['user_id'] = user.id session['user_id'] = user.id
session['is_admin'] = user.is_admin session['is_admin'] = user.is_admin
@ -182,28 +188,25 @@ def login():
return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'}) return jsonify({'status': 'error', 'message': 'Invalid credentials. Please try again.'})
return render_template('login.html') return render_template('login.html')
@app.route('/change_password', methods=['GET', 'POST']) @app.route('/change_password', methods=['GET', 'POST'])
def change_password(): def change_password():
if request.method == 'POST': if request.method == 'POST':
username = request.form['username'].lower() username = request.form['username'].lower()
current_password = request.form['current_password'] current_password = request.form['current_password']
new_password = request.form['new_password'] new_password = request.form['new_password']
user = User.query.filter_by(username=username).first() user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, current_password): if user:
hashed_new_password = generate_password_hash(new_password) current_salt, current_hash = user.password.split('$')
user.password = hashed_new_password if current_hash == hashlib.sha256((current_salt + current_password).encode()).hexdigest():
db.session.commit() new_hashed_password = sha256_password_hash(new_password)
logger.info(f"Password changed for user: {username}") user.password = new_hashed_password
return jsonify({'status': 'Password changed successfully'}) 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}") logger.warning(f"Password change failed for user: {username}")
return jsonify({'status': 'error'}) return jsonify({'status': 'error'})
return render_template('change_password.html') return render_template('change_password.html')
# Update your admin check accordingly
@app.route('/admin', methods=['GET']) @app.route('/admin', methods=['GET'])
def admin_panel(): def admin_panel():
if 'username' in session: if 'username' in session:
@ -217,22 +220,20 @@ def admin_panel():
def admin_change_password(): def admin_change_password():
if 'username' in session: if 'username' in session:
admin_user = User.query.filter_by(username=session['username']).first() admin_user = User.query.filter_by(username=session['username']).first()
logger.debug(f"Admin User: {admin_user.username}, Is Admin: {admin_user.is_admin}") if admin_user and admin_user.is_admin:
if admin_user and admin_user.is_admin: # Check if the user is an admin
try: try:
data = request.get_json() # Parse the JSON data from the request data = request.get_json()
user_id = data['user_id'] user_id = data['user_id']
new_password = data['new_password'] new_password = data['new_password']
user = User.query.get(user_id) user = User.query.get(user_id)
if user: if user:
user.password = generate_password_hash(new_password) user.password = sha256_password_hash(new_password)
db.session.commit() db.session.commit()
return jsonify({'status': 'success'}) return jsonify({'status': 'success'})
return jsonify({'status': 'user not found'}), 404 return jsonify({'status': 'user not found'}), 404
except Exception as e: except Exception as e:
logger.error(f"Error changing password: {e}") logger.error(f"Error changing password: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error', 'message': str(e)}), 500
logger.warning(f"Unauthorized attempt to access admin change password by user {session.get('username', 'unknown')}")
return jsonify({'status': 'error'}), 401 return jsonify({'status': 'error'}), 401
@app.route('/admin/disable_account', methods=['POST']) @app.route('/admin/disable_account', methods=['POST'])
@ -250,7 +251,6 @@ def admin_disable_account():
except Exception as e: except Exception as e:
logger.error(f"Error disabling account: {e}") logger.error(f"Error disabling account: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error', 'message': str(e)}), 500
logger.warning(f"Unauthorized attempt to disable account by user {session.get('username', 'unknown')}")
return jsonify({'status': 'error'}), 401 return jsonify({'status': 'error'}), 401
@app.route('/admin/enable_account', methods=['POST']) @app.route('/admin/enable_account', methods=['POST'])
@ -268,7 +268,6 @@ def admin_enable_account():
except Exception as e: except Exception as e:
logger.error(f"Error enabling account: {e}") logger.error(f"Error enabling account: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500 return jsonify({'status': 'error', 'message': str(e)}), 500
logger.warning(f"Unauthorized attempt to enable account by user {session.get('username', 'unknown')}")
return jsonify({'status': 'error'}), 401 return jsonify({'status': 'error'}), 401
@app.route('/dashboard') @app.route('/dashboard')
@ -281,9 +280,8 @@ def dashboard():
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all() pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all()
messages = Message.query.filter_by(receiver_id=user.id).all() # Updated line messages = Message.query.filter_by(receiver_id=user.id).all()
# Fetch groups for the user
group_memberships = GroupMember.query.filter_by(user_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] group_ids = [membership.group_id for membership in group_memberships]
groups = Group.query.filter(Group.id.in_(group_ids)).all() groups = Group.query.filter(Group.id.in_(group_ids)).all()
@ -315,13 +313,12 @@ def chat(friend_id):
friend_user = User.query.filter_by(id=friend_id).first() friend_user = User.query.filter_by(id=friend_id).first()
user = User.query.filter_by(id=current_user_id).first() user = User.query.filter_by(id=current_user_id).first()
friends = Friend.query.filter_by(user_id=user.id).all() friends = Friend.query.filter_by(user_id=user.id).all()
profile_picture = user.profile_picture # Assuming this is the path to the profile picture profile_picture = user.profile_picture
friend_ids = [f.friend_id for f in friends] friend_ids = [f.friend_id for f in friends]
friend_users = User.query.filter(User.id.in_(friend_ids)).all() friend_users = User.query.filter(User.id.in_(friend_ids)).all()
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all() friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
# Fetch groups for the user
group_memberships = GroupMember.query.filter_by(user_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] group_ids = [membership.group_id for membership in group_memberships]
groups = Group.query.filter(Group.id.in_(group_ids)).all() groups = Group.query.filter(Group.id.in_(group_ids)).all()
@ -366,9 +363,9 @@ def send_message(receiver):
decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content
socketio.emit('new_message', { socketio.emit('new_message', {
'sender': session['username'], 'sender': session['username'],
'receiver': receiver_user.username, # Include the receiver information 'receiver': receiver_user.username,
'content': decrypted_content, 'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Ensure this is correct 'sender_profile_picture': sender_user.profile_picture,
'content_type': content_type, 'content_type': content_type,
'timestamp': timestamp, 'timestamp': timestamp,
'id': new_message.id 'id': new_message.id
@ -380,7 +377,6 @@ def send_message(receiver):
db.session.add(pending_message) db.session.add(pending_message)
db.session.commit() db.session.commit()
logger.info(f"Pending message from {session['username']} to {receiver_user.username}") 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({'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 return jsonify({'error': 'No content or file provided'}), 400
except Exception as e: except Exception as e:
@ -430,8 +426,8 @@ def compress_image(image_path, quality=15):
if img.format == 'PNG': if img.format == 'PNG':
img.save(image_path, "PNG", optimize=True) img.save(image_path, "PNG", optimize=True)
else: else:
img = img.convert("RGB") # Ensure the image is in RGB mode for non-PNG files img = img.convert("RGB")
img.save(image_path, "JPEG", quality=quality) # Save with JPEG format and compression img.save(image_path, "JPEG", quality=quality)
@app.route('/upload_profile_picture', methods=['POST']) @app.route('/upload_profile_picture', methods=['POST'])
def upload_profile_picture(): def upload_profile_picture():
@ -446,15 +442,12 @@ def upload_profile_picture():
filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}" filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}"
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path) file.save(file_path)
# Compress the image
compress_image(file_path, quality=85) compress_image(file_path, quality=85)
user.profile_picture = filename user.profile_picture = filename
db.session.commit() db.session.commit()
logger.info(f"User {session['username']} uploaded a new profile picture: {filename}") logger.info(f"User {session['username']} uploaded a new profile picture: {filename}")
return redirect(url_for('dashboard')) # Redirect to a valid endpoint return redirect(url_for('dashboard'))
return redirect(url_for('dashboard')) # Redirect to a valid endpoint if not logged in return redirect(url_for('dashboard'))
@app.route('/cdn/<filename>') @app.route('/cdn/<filename>')
def uploaded_file(filename): def uploaded_file(filename):
@ -463,23 +456,19 @@ def uploaded_file(filename):
@app.route('/add_friend', methods=['POST']) @app.route('/add_friend', methods=['POST'])
def add_friend(): def add_friend():
if 'username' in session: if 'username' in session:
friend_username = request.form['friend_username'].lower() # Convert to lowercase friend_username = request.form['friend_username'].lower()
user = User.query.filter_by(username=session['username']).first() user = User.query.filter_by(username=session['username']).first()
friend = User.query.filter_by(username=friend_username).first() friend = User.query.filter_by(username=friend_username).first()
if friend and user != friend: if friend and user != friend:
# Check if friend request already exists
existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first() existing_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend.id).first()
if existing_request: if existing_request:
return jsonify({'status': 'Friend request already sent'}) return jsonify({'status': 'Friend request already sent'})
# Check if already friends
existing_friend = Friend.query.filter( existing_friend = Friend.query.filter(
((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) | ((Friend.user_id == user.id) & (Friend.friend_id == friend.id)) |
((Friend.user_id == friend.id) & (Friend.friend_id == user.id)) ((Friend.user_id == friend.id) & (Friend.friend_id == user.id))
).first() ).first()
if existing_friend: if existing_friend:
return jsonify({'status': 'Already friends'}) return jsonify({'status': 'Already friends'})
friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id) friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id)
db.session.add(friend_request) db.session.add(friend_request)
db.session.commit() db.session.commit()
@ -493,7 +482,6 @@ def add_friend():
return jsonify({'status': '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 return jsonify({'status': 'Unauthorized'}), 401
@app.route('/accept_friend/<int:request_id>', methods=['POST']) @app.route('/accept_friend/<int:request_id>', methods=['POST'])
def accept_friend(request_id): def accept_friend(request_id):
if 'username' in session: if 'username' in session:
@ -502,7 +490,6 @@ def accept_friend(request_id):
friend_request.status = 'accepted' friend_request.status = 'accepted'
db.session.commit() db.session.commit()
# Create friendships both ways
user_id = friend_request.receiver_id user_id = friend_request.receiver_id
friend_id = friend_request.sender_id friend_id = friend_request.sender_id
new_friend_1 = Friend(user_id=user_id, friend_id=friend_id) new_friend_1 = Friend(user_id=user_id, friend_id=friend_id)
@ -511,7 +498,6 @@ def accept_friend(request_id):
db.session.add(new_friend_2) db.session.add(new_friend_2)
db.session.commit() db.session.commit()
# Move pending messages to messages
pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all() pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all()
for pm in pending_messages: 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) new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, content_type=pm.content_type, timestamp=pm.timestamp)
@ -524,7 +510,6 @@ def accept_friend(request_id):
'receiver': friend_request.receiver.username 'receiver': friend_request.receiver.username
}, room=friend_request.sender.username) }, room=friend_request.sender.username)
logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}") logger.info(f"Friend request accepted by {session['username']} from {friend_request.sender.username}")
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}") logger.warning(f"Friend request accept failed: Friend request not found or unauthorized access by {session['username']}")
return 'Friend request not found' return 'Friend request not found'
@ -537,13 +522,11 @@ def reject_friend(request_id):
if friend_request and friend_request.receiver.username == session['username']: if friend_request and friend_request.receiver.username == session['username']:
db.session.delete(friend_request) db.session.delete(friend_request)
db.session.commit() db.session.commit()
socketio.emit('friend_request_rejected', { socketio.emit('friend_request_rejected', {
'sender': friend_request.sender.username, 'sender': friend_request.sender.username,
'receiver': friend_request.receiver.username 'receiver': friend_request.receiver.username
}, room=friend_request.sender.username) }, room=friend_request.sender.username)
logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}") logger.info(f"Friend request rejected by {session['username']} from {friend_request.sender.username}")
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}") logger.warning(f"Friend request reject failed: Friend request not found or unauthorized access by {session['username']}")
return 'Friend request not found' return 'Friend request not found'
@ -560,7 +543,6 @@ def remove_friend(friend_id):
if reciprocal_friend: if reciprocal_friend:
db.session.delete(reciprocal_friend) db.session.delete(reciprocal_friend)
# Remove friend requests associated with the friend
sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first() sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first()
if sent_request: if sent_request:
db.session.delete(sent_request) db.session.delete(sent_request)
@ -569,13 +551,11 @@ def remove_friend(friend_id):
db.session.delete(received_request) db.session.delete(received_request)
db.session.commit() db.session.commit()
socketio.emit('friend_removed', { socketio.emit('friend_removed', {
'sender': session['username'], 'sender': session['username'],
'receiver': friend.friend.username 'receiver': friend.friend.username
}, room=friend.friend.username) }, room=friend.friend.username)
logger.info(f"Friend {friend.friend.username} removed by {session['username']}") logger.info(f"Friend {friend.friend.username} removed by {session['username']}")
return jsonify({'status': 'success'}), 200 return jsonify({'status': 'success'}), 200
logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}") 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': 'Friend not found or unauthorized access'}), 404
@ -584,30 +564,45 @@ def remove_friend(friend_id):
@app.route('/create_group', methods=['POST']) @app.route('/create_group', methods=['POST'])
def create_group(): def create_group():
if 'username' in session: if 'username' in session:
user = User.query.filter_by(username=session['username']).first() try:
group_name = request.form['group_name'] user = User.query.filter_by(username=session['username']).first()
member_usernames = [username.strip() for username in request.form['members'].split(',')] group_name = request.form['group_name']
member_usernames = [username.strip() for username in request.form['members'].split(',')]
# Create a new group existing_group = Group.query.filter_by(name=group_name).first()
new_group = Group(name=group_name, admin_id=user.id) if existing_group:
db.session.add(new_group) return jsonify({'status': 'Error', 'message': 'A group with this name already exists.'}), 400
db.session.commit()
# Add the group creator as a member for username in member_usernames:
new_group_member = GroupMember(group_id=new_group.id, user_id=user.id) member = User.query.filter_by(username=username).first()
db.session.add(new_group_member) 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
# Add other members to the group new_group = Group(name=group_name, admin_id=user.id)
for username in member_usernames: db.session.add(new_group)
member = User.query.filter_by(username=username).first() db.session.flush()
if member:
new_group_member = GroupMember(group_id=new_group.id, user_id=member.id) new_group_member = GroupMember(group_id=new_group.id, user_id=user.id)
db.session.add(new_group_member) db.session.add(new_group_member)
db.session.commit() for username in member_usernames:
member = User.query.filter_by(username=username).first()
return redirect(url_for('dashboard')) if member:
return redirect(url_for('login')) 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']) @app.route('/invite_to_group', methods=['POST'])
def invite_to_group(): def invite_to_group():
@ -672,12 +667,10 @@ def send_group_message(group_id):
encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else 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) timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc)
# Fetch the sender user object
sender_user = User.query.filter_by(username=session['username']).first() sender_user = User.query.filter_by(username=session['username']).first()
if not sender_user: if not sender_user:
return jsonify({'error': 'User not found'}), 404 return jsonify({'error': 'User not found'}), 404
# Create new group message
new_message = GroupMessage(group_id=group_id, sender_id=sender_user.id, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) 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.add(new_message)
db.session.commit() db.session.commit()
@ -687,7 +680,7 @@ def send_group_message(group_id):
'group_id': group_id, 'group_id': group_id,
'sender': session['username'], 'sender': session['username'],
'content': decrypted_content, 'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Add profile picture 'sender_profile_picture': sender_user.profile_picture,
'content_type': content_type, 'content_type': content_type,
'timestamp': timestamp, 'timestamp': timestamp,
'id': new_message.id 'id': new_message.id
@ -710,8 +703,8 @@ def get_group_messages(group_id):
{ {
'id': msg.GroupMessage.id, 'id': msg.GroupMessage.id,
'group_id': msg.GroupMessage.group_id, 'group_id': msg.GroupMessage.group_id,
'sender': msg.User.username, # Use the username from the User table 'sender': msg.User.username,
'sender_profile_picture': msg.User.profile_picture, # Add profile picture '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': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content,
'content_type': msg.GroupMessage.content_type, 'content_type': msg.GroupMessage.content_type,
'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S") 'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S")
@ -750,6 +743,26 @@ def settings():
return render_template('settings.html', current_theme=current_theme) return render_template('settings.html', current_theme=current_theme)
return redirect(url_for('login')) 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') @app.route('/logout')
def logout(): def logout():
session.pop('username', None) session.pop('username', None)
@ -759,12 +772,17 @@ def logout():
logger.info(f"User logged out") logger.info(f"User logged out")
return response return response
@socketio.on('ping')
def handle_ping():
emit('pong')
@socketio.on('connect') @socketio.on('connect')
def handle_connect(): def handle_connect():
if 'username' in session: if 'username' in session:
user = User.query.filter_by(username=session['username']).first() user = User.query.filter_by(username=session['username']).first()
if user: if user:
user.online = True user.online = True
user.last_activity = datetime.utcnow()
db.session.commit() db.session.commit()
emit('user_online', {'username': user.username}, broadcast=True) emit('user_online', {'username': user.username}, broadcast=True)
logger.info(f"User {session['username']} connected") logger.info(f"User {session['username']} connected")
@ -805,7 +823,24 @@ def handle_leave_group(data):
leave_room(f"group_{group_id}") leave_room(f"group_{group_id}")
logger.info(f"User {username} left group room {group_id}") logger.info(f"User {username} left group room {group_id}")
@socketio.on('user_activity')
def handle_user_activity():
if 'username' in session:
user = User.query.filter_by(username=session['username']).first()
if user:
user.last_activity = datetime.utcnow()
db.session.commit()
def set_users_to_idle():
with app.app_context():
idle_threshold = datetime.utcnow() - timedelta(minutes=5)
users = User.query.filter(User.online == True, User.last_activity < idle_threshold).all()
for user in users:
user.online = False
db.session.commit()
emit('user_idle', {'username': user.username}, broadcast=True)
if __name__ == '__main__': if __name__ == '__main__':
with app.app_context(): with app.app_context():
db.create_all() db.create_all()
socketio.run(app, host='0.0.0.0', port=8086, debug=True, allow_unsafe_werkzeug=True) socketio.run(app, host='0.0.0.0', port=8086, debug=True, allow_unsafe_werkzeug=True)

View file

@ -121,4 +121,12 @@ hr{
height: 1px; height: 1px;
border: none; border: none;
@apply !bg-[#f1f1f1] dark:!bg-[#363636] @apply !bg-[#f1f1f1] dark:!bg-[#363636]
}
.button-admin{
@apply bg-transparent border-solid border-1 border-black dark:border-white p-2 shadow-lg
}
.button-admin:disabled{
@apply bg-neutral-400 text-neutral-200 dark:bg-neutral-700 dark:text-neutral-500
} }

View file

@ -18,7 +18,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
socket.emit('join', { username: username }); socket.emit('join', { username: username });
document.title = `Chatting with ${friendUsername}`; document.title = `${friendUsername}`;
const messagesList = document.getElementById('messages'); const messagesList = document.getElementById('messages');
const imageOverlay = document.getElementById('image-overlay'); const imageOverlay = document.getElementById('image-overlay');
@ -147,7 +147,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
// Include the profile picture if available // Include the profile picture if available
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : ''; const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`; usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -179,7 +179,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
messagesList.innerHTML = ''; messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `Chatting with ${friendUsername}`; document.getElementById('chat-with').textContent = `${friendUsername}`;
document.getElementById('send-message-form').dataset.receiver = friendId; document.getElementById('send-message-form').dataset.receiver = friendId;
data.messages.forEach((msg, index, messages) => { data.messages.forEach((msg, index, messages) => {
@ -261,7 +261,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
const timestampFormatted = formatLocalTime(timestamp); const timestampFormatted = formatLocalTime(timestamp);
const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`; usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong><div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -436,21 +436,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
document.addEventListener('click', function() { document.addEventListener('click', function() {
contextMenu.style.display = 'none'; contextMenu.style.display = 'none';
}); });
// Handle user online/offline status
socket.on('user_online', function(data) {
const statusIndicator = document.getElementById(`status-${data.username}`);
if (statusIndicator) {
statusIndicator.classList.remove('offline');
statusIndicator.classList.add('online');
}
});
socket.on('user_offline', function(data) {
const statusIndicator = document.getElementById(`status-${data.username}`);
if (statusIndicator) {
statusIndicator.classList.remove('online');
statusIndicator.classList.add('offline');
}
});
}); });

View file

@ -0,0 +1,33 @@
document.addEventListener('DOMContentLoaded', (event) => {
const socket = io();
let isConnected = true;
function checkConnection() {
socket.emit('ping');
}
socket.on('pong', function () {
isConnected = true;
});
setInterval(() => {
if (!isConnected) {
if (confirm('Something went wrong, please reload the website')) {
window.location.reload();
}
}
isConnected = false;
checkConnection();
}, 3000);
socket.on('connect', () => {
isConnected = true;
});
socket.on('disconnect', () => {
isConnected = false;
if (confirm('Something went wrong, please reload the website')) {
window.location.reload();
}
});
});

View file

@ -1,24 +1,6 @@
document.addEventListener('DOMContentLoaded', (event) => { document.addEventListener('DOMContentLoaded', (event) => {
const socket = io(); const socket = io();
socket.on('user_online', function(data) {
const username = data.username;
const friendElement = document.querySelector(`[data-username="${username}"]`);
if (friendElement) {
friendElement.querySelector('.status-indicator').classList.remove('offline');
friendElement.querySelector('.status-indicator').classList.add('online');
}
});
socket.on('user_offline', function(data) {
const username = data.username;
const friendElement = document.querySelector(`[data-username="${username}"]`);
if (friendElement) {
friendElement.querySelector('.status-indicator').classList.remove('online');
friendElement.querySelector('.status-indicator').classList.add('offline');
}
});
function checkFriendRequests() { function checkFriendRequests() {
const friendRequestsList = document.getElementById('friend-requests'); const friendRequestsList = document.getElementById('friend-requests');
const friendRequestsSection = document.getElementById('friend-requests-section'); const friendRequestsSection = document.getElementById('friend-requests-section');

View file

@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
const md = window.markdownit(); const md = window.markdownit();
socket.emit('join_group', { group_id: groupId }); socket.emit('join_group', { group_id: groupId });
document.title = `Group Chat: ${groupName}`; document.title = `${groupName}`;
const messagesList = document.getElementById('messages'); const messagesList = document.getElementById('messages');
const imageOverlay = document.getElementById('image-overlay'); const imageOverlay = document.getElementById('image-overlay');
@ -76,7 +76,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
const profilePictureUrl = `/cdn/${data.sender_profile_picture}`; const profilePictureUrl = `/cdn/${data.sender_profile_picture}`;
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture"> <strong>${data.sender}</strong>:<div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`; usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture enhanceable-image"> <strong>${data.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
}) })
.then(data => { .then(data => {
messagesList.innerHTML = ''; messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `Group Chat: ${groupName}`; document.getElementById('chat-with').textContent = `${groupName}`;
document.getElementById('send-message-form').dataset.receiver = groupId; document.getElementById('send-message-form').dataset.receiver = groupId;
data.messages.forEach((msg, index, messages) => { data.messages.forEach((msg, index, messages) => {
@ -128,7 +128,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
usernameElement.classList.add('message-myself-sender'); usernameElement.classList.add('message-myself-sender');
} }
const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`; const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`; usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture enhanceable-image"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -345,21 +345,4 @@ document.addEventListener('DOMContentLoaded', (event) => {
document.addEventListener('click', function() { document.addEventListener('click', function() {
contextMenu.style.display = 'none'; contextMenu.style.display = 'none';
}); });
// Handle user online/offline status
socket.on('user_online', function(data) {
const statusIndicator = document.getElementById(`status-${data.username}`);
if (statusIndicator) {
statusIndicator.classList.remove('offline');
statusIndicator.classList.add('online');
}
});
socket.on('user_offline', function(data) {
const statusIndicator = document.getElementById(`status-${data.username}`);
if (statusIndicator) {
statusIndicator.classList.remove('online');
statusIndicator.classList.add('offline');
}
});
}); });

View file

@ -0,0 +1,20 @@
document.addEventListener('DOMContentLoaded', (event) => {
const socket = io();
// Handle user online/offline status
socket.on('user_online', function(data) {
const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`);
if (statusIndicator) {
statusIndicator.classList.remove('offline');
statusIndicator.classList.add('online');
}
});
socket.on('user_offline', function(data) {
const statusIndicator = document.querySelector(`.status-indicator[data-username="${data.username}"]`);
if (statusIndicator) {
statusIndicator.classList.remove('online');
statusIndicator.classList.add('offline');
}
});
});

View file

@ -616,6 +616,10 @@ video {
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
} }
.ml-10 {
margin-left: 2.5rem;
}
.mr-5 { .mr-5 {
margin-right: 1.25rem; margin-right: 1.25rem;
} }
@ -640,6 +644,10 @@ video {
height: 400px !important; height: 400px !important;
} }
.\!h-\[460px\] {
height: 460px !important;
}
.\!h-\[465px\] { .\!h-\[465px\] {
height: 465px !important; height: 465px !important;
} }
@ -686,6 +694,10 @@ video {
width: 100%; width: 100%;
} }
.min-w-\[280px\] {
min-width: 280px;
}
.flex-1 { .flex-1 {
flex: 1 1 0%; flex: 1 1 0%;
} }
@ -1203,6 +1215,37 @@ hr:is(.dark *) {
background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important; background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important;
} }
.button-admin {
border-style: solid;
--tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity));
background-color: transparent;
padding: 0.5rem;
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
border-width: 1px;
}
.button-admin:is(.dark *) {
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
.button-admin:disabled {
--tw-bg-opacity: 1;
background-color: rgb(163 163 163 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(229 229 229 / var(--tw-text-opacity));
}
.button-admin:disabled:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(64 64 64 / var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity));
}
.hover\:\!text-slate-400:hover { .hover\:\!text-slate-400:hover {
--tw-text-opacity: 1 !important; --tw-text-opacity: 1 !important;
color: rgb(148 163 184 / var(--tw-text-opacity)) !important; color: rgb(148 163 184 / var(--tw-text-opacity)) !important;

View file

@ -358,6 +358,10 @@ ul#friends-list {
background-color: #80848e; background-color: #80848e;
} }
.idle {
background-color: #f0b232;
}
/* Custom Scroll Bar */ /* Custom Scroll Bar */
::-webkit-scrollbar { ::-webkit-scrollbar {

View file

@ -69,19 +69,28 @@
<table class="table dark:!text-white text-black"> <table class="table dark:!text-white text-black">
<thead> <thead>
<tr> <tr>
<th>ID</th>
<th>Username</th> <th>Username</th>
<th>Actions</th> <th>Change Password</th>
<th>Disable Account</th>
<th>Online?</th>
<th>Admin?</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for user in users %} {% for user in users %}
<tr> <tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td> <td>{{ user.username }}</td>
<td> <td>
<button class="btn btn-warning" onclick="changePassword({{ user.id }})">Change Password</button> <button class="btn btn-warning" onclick="changePassword({{ user.id }})">Change Password</button>
<button onclick="disableAccount({{ user.id }})" {% if user.disabled %}disabled{% endif %}>Disable</button>
<button onclick="enableAccount({{ user.id }})" {% if not user.disabled %}disabled{% endif %}>Enable</button>
</td> </td>
<td>
<button class="button-admin" onclick="disableAccount({{ user.id }})" {% if user.disabled %}disabled{% endif %}>Disable</button>
<button class="button-admin" onclick="enableAccount({{ user.id }})" {% if not user.disabled %}disabled{% endif %}>Enable</button>
</td>
<td>{{ user.online }}</td>
<td>{{ user.is_admin }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -1,3 +1,3 @@
<div class="bg-white h-16 border-solid border-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]"> <div class="bg-white h-16 border-solid border-l-1 border-r-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200"> Chatting with {{ friend_username }}</h3> <i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200">{{ friend_username }}</h3>
</div> </div>

View file

@ -1,3 +1,3 @@
<div class="bg-white h-16 border-solid border-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]"> <div class="bg-white h-16 border-solid border-l-1 border-r-1 border-[#cad6d8] dark:!border-[#595959] flex items-center dark:bg-[#242424]">
<i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200"> Group Chat: {{ group.name }}</h3> <i class="fa-solid fa-hashtag dark:!text-neutral-200"></i><h3 id="chat-with" class="dark:!text-neutral-200">{{ group.name }}</h3>
</div> </div>

26
templates/custom_css.html Normal file
View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom CSS</title>
{% include 'head.html' %}
</head>
<body class="login-body">
<div class="login-div !h-[460px]">
<form class="login-form" id="custom-css-form" method="POST">
<h2 class="login-h2">Custom CSS</h2>
<div class="login-input-div-first">
<label for="custom_css" class="login-label">Enter your custom CSS:</label>
<textarea class="login-input" id="custom_css" name="custom_css" rows="10">{{ custom_css }}</textarea>
</div>
<div class="login-input-div-first !flex-row">
<a class="no-account no-account-button" href="{{ url_for('dashboard') }}"> Go Back</a>
</div>
<div class="login-input-div-first items-end">
<button class="ms-button-signin" type="submit" class="btn btn-primary">Save CSS</button>
</div>
</form>
<div id="message" class="mt-3"></div>
</div>
</body>
</html>

View file

@ -43,15 +43,50 @@
</form> </form>
</div> </div>
<!-- <div> {% if is_admin %}
<h4 class="text-black dark:!text-white"X>Create Group</h4> <div class="dark:!bg-[#1f1f1f] shadow-lg bg-[#f1f1f1] w-fit flex ml-10 flex-col rounded-md items-center">
<form id="create-group-form" method="POST"> <h4 class="text-black dark:!text-white relative top-2">Create Group</h4>
<input type="text" name="group_name" placeholder="Group Name" required autocomplete="off"> <form id="create-group-form" method="POST" class="w-fit flex flex-col items-center justify-center">
<input type="text" name="members" placeholder="Member Usernames (comma separated)" required autocomplete="off"> <input class="dark:!bg-[#1f1f1f] bg-[#f1f1f1] pt-5 rounded-t-md w-3/4" type="text" name="group_name" placeholder="Group Name" required autocomplete="off">
<button class="ms-button" type="submit">Create Group</button> <input class="dark:!bg-[#1f1f1f] bg-[#f1f1f1] pt-5 rounded-t-md w-full min-w-[280px]" type="text" name="members" placeholder="Member Usernames (comma separated)" required autocomplete="off">
<div id="create-group-message" style="color: green;"></div> <button class="ms-button" type="submit">Create Group</button>
</form> <div id="create-group-message" style="color: green;"></div>
</div> --> </form>
</div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
const createGroupForm = document.getElementById('create-group-form');
if (createGroupForm) {
createGroupForm.addEventListener('submit', function(event) {
event.preventDefault();
const formData = new FormData(createGroupForm);
const members = formData.get('members').split(',').map(member => member.trim());
formData.set('members', members.join(','));
fetch('/create_group', {
method: 'POST',
body: formData
}).then(response => response.json())
.then(data => {
if (data.status === 'Group created') {
alert('Group created successfully');
location.reload();
} else {
alert('Error creating group: ' + data.message);
}
})
.catch(error => {
alert('Error creating group: ' + error.message);
});
});
}
});
</script>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,6 @@
<meta charset="UTF-8">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> --> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='output.css') }}">
@ -9,6 +10,7 @@
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script> <script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/47dbf3c43e.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ url_for('static', filename='js/connection_check.js') }}"></script>
<script> <script>
// Function to apply the theme based on user selection or system preference // Function to apply the theme based on user selection or system preference
@ -33,4 +35,9 @@
}); });
} }
}); });
</script> </script>
{% if 'username' in session %}
<style>
{{ current_user.custom_css | safe }}
</style>
{% endif %}

View file

@ -1,3 +1,4 @@
<script src="{{ url_for('static', filename='js/online_detection.js') }}"></script>
<div class="sidebar w-fit bg-transparent text-white flex flex-col items-center"> <div class="sidebar w-fit bg-transparent text-white flex flex-col items-center">
<div class="sidebar-content h-full w-60 p-5 rounded-t-lg"> <div class="sidebar-content h-full w-60 p-5 rounded-t-lg">
<h4 class="flex flex-row items-center"> <h4 class="flex flex-row items-center">
@ -11,7 +12,7 @@
<ul id="friends-list"> <ul id="friends-list">
{% for friend in friends %} {% for friend in friends %}
<li data-id="{{ friend.id }}" data-username="{{ friend.username }}"> <li data-id="{{ friend.id }}" data-username="{{ friend.username }}">
<span class="status-indicator {{ 'online' if friend.online else 'offline' }}"></span> <span class="status-indicator {{ 'online' if friend.online else 'offline' }}" data-username="{{ friend.username }}"></span>
<a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a> <a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a>
</li> </li>
{% endfor %} {% endfor %}
@ -113,4 +114,4 @@
}); });
} }
}); });
</script> </script>

View file

@ -8,10 +8,14 @@
<p>{{ username }}</p> <p>{{ username }}</p>
<a href="/settings"><i class="fa-solid fa-gear"></i> Settings</a> <a href="/settings"><i class="fa-solid fa-gear"></i> Settings</a>
<a href="/change_password"><i class="fa-solid fa-lock"></i> Change Password</a> <a href="/change_password"><i class="fa-solid fa-lock"></i> Change Password</a>
<a href="{{ url_for('custom_css') }}"><i class="fa-brands fa-css3-alt"></i> Custom CSS</a>
{% if is_admin %} {% if is_admin %}
<a href="/admin"><i class="fa-solid fa-shield-alt"></i> Admin Panel</a> <a href="/admin"><i class="fa-solid fa-shield-alt"></i> Admin Panel</a>
{% endif %} {% endif %}
<a href="/logout"><i class="fa-solid fa-right-from-bracket"></i> Logout</a> <a href="/logout"><i class="fa-solid fa-right-from-bracket"></i> Logout</a>
</div> </div>
</div> </div>