# This file includes the handlers for sending messages from user-to-user. import json # Import the file format library for data. import uuid # Imports this library to generate unique UUIDs. import time # Import this library to check the time. import asyncio # Imports this library to allow running # multiple tasks simultaneously. # Define the handler function to send chat messages. async def send_chat_message(websocket, auth_data, username, USERS_DB, CONVERSATIONS_DB, RECENTS_DB, ONLINE_USERS, OFFLINE_QUEUES_DB, file_lock, conversation_key, save_conversations_sync, save_recents_sync, update_recent_chat_entry): # Initializing important variables. recipient = auth_data.get("target") text = auth_data.get("message", "") sender_display = USERS_DB[username].get("display_name", username) # Checks if the user isn't in the database somehow. # e.g: Account deletion, database desync. if recipient not in USERS_DB: await websocket.send_text("[-] ERROR: Unable to send message; User not found.") # Send an error emssage alert. return # Checks for malicious edits if the text is over 10 000 characters long. if len(text) > 10000: text = text[:10000] # Cuts off the text if it detects text over 10 000 chars. is_online = recipient in ONLINE_USERS # Checks if the recipient is online. msg_id = str(uuid.uuid4()) # Generates a unique message id. formatted_message = { # Message format "id": msg_id, "sender": username, "senderDisplayname": sender_display, "target": recipient, "message": text, "timestamp": time.time(), "status": "delivered" if is_online else "sent" } conv_key = conversation_key(username, recipient) # Unique convo string w/ usernames. # Saves the convo to the local database. async with file_lock: if conv_key not in CONVERSATIONS_DB: CONVERSATIONS_DB[conv_key] = [] CONVERSATIONS_DB[conv_key].append(formatted_message) update_recent_chat_entry(username, recipient, text, formatted_message["timestamp"], sender_display) update_recent_chat_entry(recipient, username, text, formatted_message["timestamp"], sender_display) await asyncio.to_thread(save_recents_sync) # Syncs the local conversation db to the main database. outgoing_payload = json.dumps({ "action": "new_message", "id": msg_id, "sender": username, "senderDisplayname": sender_display, "message": text, "timestamp": formatted_message["timestamp"], "status": formatted_message["status"] }) if is_online: await ONLINE_USERS[recipient].send_text(outgoing_payload) else: if recipient not in OFFLINE_QUEUES_DB: OFFLINE_QUEUES_DB[recipient] = [] OFFLINE_QUEUES_DB[recipient].append(outgoing_payload) if username in ONLINE_USERS: await ONLINE_USERS[username].send_text(json.dumps({ "action": "recent_chats_updated", "recent_chats": RECENTS_DB.get(username, []) })) if recipient in ONLINE_USERS: await ONLINE_USERS[recipient].send_text(json.dumps({ "action": "recent_chats_updated", "recent_chats": RECENTS_DB.get(recipient, []) })) await asyncio.to_thread(save_conversations_sync) return async def request_chat_history(auth_data, websocket, username, conversation_key, CONVERSATIONS_DB): target_partner = auth_data.get("target") conv_key = conversation_key(username, target_partner) history = CONVERSATIONS_DB.get(conv_key, []) sorted_history = sorted(history, key=lambda x: x.get("timestamp", 0)) await websocket.send_text(json.dumps({ "action": "load_history_results", "results": sorted_history })) return async def mark_seen(auth_data, conversation_key, username, file_lock, CONVERSATIONS_DB, save_conversations_sync, ONLINE_USERS): partner = auth_data.get("target") conv_key = conversation_key(username, partner) async with file_lock: if conv_key in CONVERSATIONS_DB: for msg in CONVERSATIONS_DB[conv_key]: if msg.get("sender") == partner and msg.get("status") != "seen": msg["status"] = "seen" await asyncio.to_thread(save_conversations_sync) if partner in ONLINE_USERS: await ONLINE_USERS[partner].send_text(json.dumps({ "action": "messages_seen", "by": username, "conv_key": conv_key })) return