diff --git a/config/db.py b/config/db.py new file mode 100644 index 0000000..fa9dc7c --- /dev/null +++ b/config/db.py @@ -0,0 +1,17 @@ +from pymongo.mongo_client import MongoClient # type: ignore + +uri = "mongodb+srv://admin1:cn242@cluster0.fum1o.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" + +client = MongoClient(uri) +db = client["user_database"] +def get_collection(collection_name): + return db[collection_name] + +users_collection = get_collection("users") +channels_collection = get_collection("channels") + +try: + client.admin.command("ping") + print("Connected to MongoDB successfully!") +except Exception as e: + print("MongoDB connection error:", e) \ No newline at end of file diff --git a/controller/authController.py b/controller/authController.py new file mode 100644 index 0000000..8c825ba --- /dev/null +++ b/controller/authController.py @@ -0,0 +1,29 @@ +import json +from models.authModel import UserRegister, UserLogin, Visitor +from services.authService import register_user, login_user, visitor_mode, logout_user + +def register(data): + user = UserRegister(**data) + result = register_user(user) + + return json.dumps(result) + +def login(data, peer_ip, peer_port): + user = UserLogin(**data) + result = login_user(user, peer_ip, peer_port) + return json.dumps(result) + + +def visitor(data): + visitor_data = Visitor(**data) + result = visitor_mode(visitor_data) + + return json.dumps(result) + +def logout(data): + session_id = data.get("session_id") + if not session_id: + return json.dumps({"status": "error", "message": "Session ID is required"}) + + result = logout_user(session_id) + return json.dumps(result) \ No newline at end of file diff --git a/controller/channelController b/controller/channelController new file mode 100644 index 0000000..030f046 --- /dev/null +++ b/controller/channelController @@ -0,0 +1,82 @@ + +from services.channelService import create_channel, join_channel, send_message, get_channel_info, get_joined_channels, get_hosted_channels, delete_channel, get_all_channels + +############################################################################################## +def create_channel_controller(data): + host = data.get("host") + channel_name = data.get("channel_name") + + if not host or not channel_name: + return {"status": "error", "message": "Missing parameters"} + + result = create_channel(host, channel_name) + return result + +############################################################################################## +def join_channel_controller(data): + username = data.get("username") + channel_name = data.get("channel_name") + + if not username or not channel_name: + return {"status": "error", "message": "Missing parameters"} + + result = join_channel(username, channel_name) + return result + +############################################################################################## +def get_user_channels_controller(data): + username = data.get("username") + + if not username: + return {"status": "error", "message": "Missing parameters"} + + joined_result = get_joined_channels(username) + hosted_result = get_hosted_channels(username) + + if joined_result["status"] == "error" or hosted_result["status"] == "error": + return {"status": "error", "message": "User not found"} + + return { + "status": "success", + "data": { + "joined_channels": joined_result.get("joined_channels", []), + "hosted_channels": hosted_result.get("hosted_channels", []) + } + } + +############################################################################################## +def send_message_controller(data): + username = data.get("username") + channel_name = data.get("channel_name") + message_text = data.get("message") + + if not username or not channel_name or not message_text: + return {"status": "error", "message": "Missing parameters"} + + result = send_message(username, channel_name, message_text) + return result + +############################################################################################## +def get_channel_info_controller(data): + channel_name = data.get("channel_name") + + if not channel_name: + return {"status": "error", "message": "Missing parameters"} + + result = get_channel_info(channel_name) + return result +############################################################################################## +def delete_channel_controller(data): + username = data.get("username") + channel_name = data.get("channel_name") + + if not username or not channel_name: + return {"status": "error", "message": "Missing parameters"} + + result = delete_channel(username, channel_name) + return result + +############################################################################################## +def get_all_channels_controller(data): + result = get_all_channels() + return result diff --git a/models/authModel.py b/models/authModel.py new file mode 100644 index 0000000..cf9ab0d --- /dev/null +++ b/models/authModel.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel # type: ignore +from typing import List, Dict + +class UserRegister(BaseModel): + username: str + email: str + password: str + +class Visitor(BaseModel): + name: str + +class UserLogin(BaseModel): + username: str + password: str + channels_joined: List[str] = [] + hosted_channels: List[str] = [] + sessions: List[Dict[str, str]] = [] \ No newline at end of file diff --git a/models/channelModel.py b/models/channelModel.py new file mode 100644 index 0000000..e69de29 diff --git a/request/authRequest.py b/request/authRequest.py new file mode 100644 index 0000000..26918da --- /dev/null +++ b/request/authRequest.py @@ -0,0 +1,86 @@ +from config.db import users_collection +import uuid +from datetime import datetime, timezone +from models.authModel import UserRegister, UserLogin, Visitor +########################################################## +def register_user(user: UserRegister) -> dict: + try: + if users_collection.find_one({"email": user.email}): + return {"status": "error", "message": "Email already registered"} + + if users_collection.find_one({"username": user.username}): + return {"status": "error", "message": "Username already taken"} + + new_user = user.model_dump() + new_user["verified"] = True + + result = users_collection.insert_one(new_user) + return {"status": "success", "message": "User registered successfully", "user_id": str(result.inserted_id)} + + except Exception as e: + return {"status": "error", "message": f"Database error: {str(e)}"} + +########################################################## +def login_user(user: UserLogin, peer_ip: str, peer_port: int) -> dict: + user_data = users_collection.find_one({"username": user.username, "password": user.password}) + + if user_data: + session_id = str(uuid.uuid4()) + new_session = { + "peer_ip": peer_ip, + "peer_port": peer_port, + "session_id": session_id, + "login_time": datetime.now(timezone.utc).isoformat() + } + + users_collection.update_one( + {"username": user.username}, + {"$push": {"sessions": new_session}} + ) + user_data = users_collection.find_one({"username": user.username}) + + def serialize(obj): + if isinstance(obj, datetime): + return obj.isoformat() + return obj + + return { + "status": "success", + "message": "Login successful", + "user": { + "username": user_data["username"], + "email": user_data.get("email", ""), + "channels_joined": user_data.get("channels_joined", []), + "hosted_channels": user_data.get("hosted_channels", []), + "sessions": [{**session, "login_time": serialize(session["login_time"])} + for session in user_data.get("sessions", [])] + } + } + + return {"status": "error", "message": "Invalid username or password"} + +########################################################## +def visitor_mode(visitor_data: Visitor, ) -> dict: + if users_collection.find_one({"username": visitor_data.name}): + return {"status": "error", "message": "Username already taken"} + if not visitor_data.name: + return {"status": "error", "message": "Visitor name cannot be empty"} + + return {"status": "success", "message": f"Welcome, {visitor_data.name}! You are in visitor mode."} +########################################################## +def logout_user(session_id: str) -> dict: + try: + user = users_collection.find_one({"sessions.session_id": session_id}) + + if not user: + return {"status": "error", "message": "Invalid session_id"} + + users_collection.update_one( + {"_id": user["_id"]}, + {"$pull": {"sessions": {"session_id": session_id}}} + ) + + return {"status": "success", "message": "Logout successful"} + + except Exception as e: + return {"status": "error", "message": f"Database error: {str(e)}"} \ No newline at end of file diff --git a/request/channelRequest.py b/request/channelRequest.py new file mode 100644 index 0000000..2858cb2 --- /dev/null +++ b/request/channelRequest.py @@ -0,0 +1,123 @@ +from config.db import channels_collection, users_collection +from models.channelModel import Channel +############################################################################################## +def create_channel(host: str, channel_name: str): + if channels_collection.find_one({"channel_name": channel_name}): + return {"status": "error", "message": "Channel already exists"} + + new_channel = Channel( + channel_name=channel_name, + owner=host, + members=[host] + ) + + channels_collection.insert_one(new_channel.dict()) + + users_collection.update_one( + {"username": host}, + {"$addToSet": {"hosted_channels": channel_name, "joined_channels": channel_name}} + ) + + return {"status": "success", "message": f"Channel '{channel_name}' created successfully"} + +############################################################################################## +def join_channel(username: str, channel_name: str): + channel = channels_collection.find_one({"channel_name": channel_name}) + if not channel: + return {"status": "error", "message": "Channel not found"} + + if username in channel["members"]: + return {"status": "error", "message": "User already in channel"} + + channels_collection.update_one( + {"channel_name": channel_name}, + {"$push": {"members": username}} + ) + + users_collection.update_one( + {"username": username}, + {"$addToSet": {"joined_channels": channel_name}} + ) + + return {"status": "success", "message": f"{username} joined '{channel_name}'"} + +############################################################################################## +def send_message(username: str, channel_name: str, message_text: str): + channel_data = channels_collection.find_one({"channel_name": channel_name}) + if not channel_data: + return {"status": "error", "message": "Channel not found"} + + if username not in channel_data["members"]: + return {"status": "error", "message": "Only registered users can send messages"} + + new_message = {"sender": username, "text": message_text} + + channels_collection.update_one( + {"channel_name": channel_name}, + {"$push": {"messages": new_message}} + ) + + return {"status": "success", "message": "Message sent successfully"} + +############################################################################################## +def get_channel_info(channel_name: str) -> dict: + channel = channels_collection.find_one({"channel_name": channel_name}) + + if not channel: + return {"status": "error", "message": "Channel not found"} + + return { + "status": "success", + "channel_name": channel["channel_name"], + "owner": channel["owner"], + "members": channel["members"], + "messages": channel.get("messages", []) + } +############################################################################################## +def get_joined_channels(username: str): + user_data = users_collection.find_one({"username": username}, {"joined_channels": 1, "_id": 0}) + if not user_data: + return {"status": "error", "message": "User not found"} + + return {"status": "success", "joined_channels": user_data.get("joined_channels", [])} + +############################################################################################## +def get_hosted_channels(username: str): + user_data = users_collection.find_one({"username": username}, {"hosted_channels": 1, "_id": 0}) + if not user_data: + return {"status": "error", "message": "User not found"} + + return {"status": "success", "hosted_channels": user_data.get("hosted_channels", [])} +############################################################################################## +def delete_channel(username: str, channel_name: str): + channel = channels_collection.find_one({"channel_name": channel_name}) + + if not channel: + return {"status": "error", "message": "Channel not found"} + + if channel["owner"] != username: + return {"status": "error", "message": "Only the owner can delete the channel"} + + channels_collection.delete_one({"channel_name": channel_name}) + + users_collection.update_many( + {}, + { + "$pull": { + "hosted_channels": channel_name, + "joined_channels": channel_name + } + } + ) + + return {"status": "success", "message": f"Channel '{channel_name}' deleted successfully"} +############################################################################################## +def get_all_channels(): + channels = channels_collection.find({}, {"_id": 0, "channel_name": 1, "owner": 1}) + + all_channels = list(channels) + + return { + "status": "success", + "data": all_channels + } \ No newline at end of file diff --git a/services/authService.py b/services/authService.py new file mode 100644 index 0000000..26918da --- /dev/null +++ b/services/authService.py @@ -0,0 +1,86 @@ +from config.db import users_collection +import uuid +from datetime import datetime, timezone +from models.authModel import UserRegister, UserLogin, Visitor +########################################################## +def register_user(user: UserRegister) -> dict: + try: + if users_collection.find_one({"email": user.email}): + return {"status": "error", "message": "Email already registered"} + + if users_collection.find_one({"username": user.username}): + return {"status": "error", "message": "Username already taken"} + + new_user = user.model_dump() + new_user["verified"] = True + + result = users_collection.insert_one(new_user) + return {"status": "success", "message": "User registered successfully", "user_id": str(result.inserted_id)} + + except Exception as e: + return {"status": "error", "message": f"Database error: {str(e)}"} + +########################################################## +def login_user(user: UserLogin, peer_ip: str, peer_port: int) -> dict: + user_data = users_collection.find_one({"username": user.username, "password": user.password}) + + if user_data: + session_id = str(uuid.uuid4()) + new_session = { + "peer_ip": peer_ip, + "peer_port": peer_port, + "session_id": session_id, + "login_time": datetime.now(timezone.utc).isoformat() + } + + users_collection.update_one( + {"username": user.username}, + {"$push": {"sessions": new_session}} + ) + user_data = users_collection.find_one({"username": user.username}) + + def serialize(obj): + if isinstance(obj, datetime): + return obj.isoformat() + return obj + + return { + "status": "success", + "message": "Login successful", + "user": { + "username": user_data["username"], + "email": user_data.get("email", ""), + "channels_joined": user_data.get("channels_joined", []), + "hosted_channels": user_data.get("hosted_channels", []), + "sessions": [{**session, "login_time": serialize(session["login_time"])} + for session in user_data.get("sessions", [])] + } + } + + return {"status": "error", "message": "Invalid username or password"} + +########################################################## +def visitor_mode(visitor_data: Visitor, ) -> dict: + if users_collection.find_one({"username": visitor_data.name}): + return {"status": "error", "message": "Username already taken"} + if not visitor_data.name: + return {"status": "error", "message": "Visitor name cannot be empty"} + + return {"status": "success", "message": f"Welcome, {visitor_data.name}! You are in visitor mode."} +########################################################## +def logout_user(session_id: str) -> dict: + try: + user = users_collection.find_one({"sessions.session_id": session_id}) + + if not user: + return {"status": "error", "message": "Invalid session_id"} + + users_collection.update_one( + {"_id": user["_id"]}, + {"$pull": {"sessions": {"session_id": session_id}}} + ) + + return {"status": "success", "message": "Logout successful"} + + except Exception as e: + return {"status": "error", "message": f"Database error: {str(e)}"} \ No newline at end of file diff --git a/services/channelService.py b/services/channelService.py new file mode 100644 index 0000000..2858cb2 --- /dev/null +++ b/services/channelService.py @@ -0,0 +1,123 @@ +from config.db import channels_collection, users_collection +from models.channelModel import Channel +############################################################################################## +def create_channel(host: str, channel_name: str): + if channels_collection.find_one({"channel_name": channel_name}): + return {"status": "error", "message": "Channel already exists"} + + new_channel = Channel( + channel_name=channel_name, + owner=host, + members=[host] + ) + + channels_collection.insert_one(new_channel.dict()) + + users_collection.update_one( + {"username": host}, + {"$addToSet": {"hosted_channels": channel_name, "joined_channels": channel_name}} + ) + + return {"status": "success", "message": f"Channel '{channel_name}' created successfully"} + +############################################################################################## +def join_channel(username: str, channel_name: str): + channel = channels_collection.find_one({"channel_name": channel_name}) + if not channel: + return {"status": "error", "message": "Channel not found"} + + if username in channel["members"]: + return {"status": "error", "message": "User already in channel"} + + channels_collection.update_one( + {"channel_name": channel_name}, + {"$push": {"members": username}} + ) + + users_collection.update_one( + {"username": username}, + {"$addToSet": {"joined_channels": channel_name}} + ) + + return {"status": "success", "message": f"{username} joined '{channel_name}'"} + +############################################################################################## +def send_message(username: str, channel_name: str, message_text: str): + channel_data = channels_collection.find_one({"channel_name": channel_name}) + if not channel_data: + return {"status": "error", "message": "Channel not found"} + + if username not in channel_data["members"]: + return {"status": "error", "message": "Only registered users can send messages"} + + new_message = {"sender": username, "text": message_text} + + channels_collection.update_one( + {"channel_name": channel_name}, + {"$push": {"messages": new_message}} + ) + + return {"status": "success", "message": "Message sent successfully"} + +############################################################################################## +def get_channel_info(channel_name: str) -> dict: + channel = channels_collection.find_one({"channel_name": channel_name}) + + if not channel: + return {"status": "error", "message": "Channel not found"} + + return { + "status": "success", + "channel_name": channel["channel_name"], + "owner": channel["owner"], + "members": channel["members"], + "messages": channel.get("messages", []) + } +############################################################################################## +def get_joined_channels(username: str): + user_data = users_collection.find_one({"username": username}, {"joined_channels": 1, "_id": 0}) + if not user_data: + return {"status": "error", "message": "User not found"} + + return {"status": "success", "joined_channels": user_data.get("joined_channels", [])} + +############################################################################################## +def get_hosted_channels(username: str): + user_data = users_collection.find_one({"username": username}, {"hosted_channels": 1, "_id": 0}) + if not user_data: + return {"status": "error", "message": "User not found"} + + return {"status": "success", "hosted_channels": user_data.get("hosted_channels", [])} +############################################################################################## +def delete_channel(username: str, channel_name: str): + channel = channels_collection.find_one({"channel_name": channel_name}) + + if not channel: + return {"status": "error", "message": "Channel not found"} + + if channel["owner"] != username: + return {"status": "error", "message": "Only the owner can delete the channel"} + + channels_collection.delete_one({"channel_name": channel_name}) + + users_collection.update_many( + {}, + { + "$pull": { + "hosted_channels": channel_name, + "joined_channels": channel_name + } + } + ) + + return {"status": "success", "message": f"Channel '{channel_name}' deleted successfully"} +############################################################################################## +def get_all_channels(): + channels = channels_collection.find({}, {"_id": 0, "channel_name": 1, "owner": 1}) + + all_channels = list(channels) + + return { + "status": "success", + "data": all_channels + } \ No newline at end of file diff --git a/tracker.py b/tracker.py new file mode 100644 index 0000000..1cca50d --- /dev/null +++ b/tracker.py @@ -0,0 +1,161 @@ +import socket +import threading +import json +from request.authRequest import handle_request as auth_request +from request.channelRequest import handle_channel_request # Import xử lý channel +from config.db import client # Kết nối MongoDB + + +class TRACKER: + def __init__(self, host="127.0.0.1", port=5000): + self.peer_list = [] # Danh sách các peer đang kết nối + self.peer_list_lock = threading.Lock() # Lock để bảo vệ truy cập danh sách + self.tracker_server(host, port) # Khởi động server + + def handle_client(self, conn, addr): + """Xử lý client kết nối đến tracker""" + user_ip, user_port = addr + print(f"New connection from {user_ip}:{user_port}") + + try: + while True: + data = conn.recv(1024).decode('utf-8').strip() + if not data: + break + + try: + command = json.loads(data) # Giải mã JSON + except json.JSONDecodeError: + conn.sendall(json.dumps({"status": "ERROR", "message": "Dữ liệu không hợp lệ"}).encode('utf-8')) + continue + + if "command" not in command: + conn.sendall(json.dumps({"status": "ERROR", "message": "Thiếu lệnh"}).encode('utf-8')) + continue + + cmd = command["command"] + + if cmd == "CONNECT": + if not all(k in command for k in ["name", "ip", "port"]): + conn.sendall(json.dumps({"status": "ERROR", "message": "Thiếu thông tin cần thiết"}).encode('utf-8')) + continue + + name, ip, port = command["name"], command["ip"], command["port"] + + with self.peer_list_lock: + if not any(n == name and i == ip and p == port for n, i, p, _ in self.peer_list): + self.peer_list.append((name, ip, port, conn)) + print(f"[INFO] Đã thêm peer: {name} ({ip}:{port})") + response = {"status": "OK", "message": f"Peer {name} ({ip}:{port}) đã được thêm"} + else: + response = {"status": "OK", "message": f"Peer {name} ({ip}:{port}) đã tồn tại"} + + conn.sendall(json.dumps(response).encode('utf-8')) + + elif cmd == "GET_LIST": + if "name" not in command: + conn.sendall(json.dumps({"status": "ERROR", "message": "Thiếu thông tin cần thiết"}).encode('utf-8')) + continue + + with self.peer_list_lock: + peer_data = [{"name": n, "ip": i, "port": p} for n, i, p, _ in self.peer_list] + + response = {"status": "OK", "peer_list": peer_data} + conn.sendall(json.dumps(response).encode('utf-8')) + print(f"[INFO] Gửi danh sách peer đến {command['name']}") + + elif cmd == "LEAVE": + if not all(k in command for k in ["name", "ip", "port"]): + conn.sendall(json.dumps({"status": "ERROR", "message": "Thiếu thông tin cần thiết"}).encode('utf-8')) + continue + + name, ip, port = command["name"], command["ip"], command["port"] + + with self.peer_list_lock: + for peer in self.peer_list: + if peer[:3] == (name, ip, port): # So sánh 3 phần tử đầu + self.peer_list.remove(peer) + print(f"[INFO] Peer {name} ({ip}:{port}) đã rời khỏi tracker.") + response = {"status": "OK", "message": f"Peer {name} ({ip}:{port}) đã rời đi"} + break + else: + response = {"status": "ERROR", "message": f"Peer {name} ({ip}:{port}) không tồn tại"} + + conn.sendall(json.dumps(response).encode('utf-8')) + + elif cmd == "MSG_SEND": + if not all(k in command for k in ["ip", "port", "name", "channel" ,"message"]): + conn.sendall(json.dumps({"status": "ERROR", "message": "Thiếu thông tin cần thiết"}).encode('utf-8')) + continue + + name, ip, port, channel ,message = command["name"], command["ip"], command["port"], command["channel"], command["message"] + print(f"[CHAT] {name} from {channel}: {message}") + + with self.peer_list_lock: + disconnected_peers = [] + #PHÂN PHỐI TIN NHẮN ĐẾN CÁC NHỮNG NGƯỜI GỬI CÓ TRONG CHANNEL + + #LẤY DANH SÁCH CÁC PEER TRONG CHANNEL + if channel == "GENERRAL": + peers_in_channel = self.peer_list + else: + pass + #DEVELOP + + + for name_list, ip_list, port_list, peer_conn in peers_in_channel: + if ip == ip_list and port == port_list: + continue # Không gửi lại cho người gửi + + try: + json_message = json.dumps({ + "command": "MSG_RECV", + "client_name": name, + "message": message + }) + + peer_conn.sendall(json_message.encode('utf-8')) + print(f"[INFO] Đã gửi tin nhắn đến {name_list} ({ip_list}:{port_list})") + + except Exception: + print(f"[ERROR] Không thể gửi tin nhắn đến {name_list} ({ip_list}:{port_list})") + disconnected_peers.append((name_list, ip_list, port_list, peer_conn)) + + # Xóa các peer đã mất kết nối + for peer in disconnected_peers: + self.peer_list.remove(peer) + print(f"[INFO] Xóa peer không còn kết nối: {peer[0]} ({peer[1]}:{peer[2]})") + + elif cmd in ["create_channel", "join_channel", "get_user_channels", "send_message", "get_channel_info", "delete_channel", "get_all_channels"]: + response = handle_channel_request(data) + conn.sendall(json.dumps(response.encode())) + + elif cmd in ["register_user","login_user","vistor_mode","logout_user"]: + response = auth_request(data, user_port, user_port) + conn.send(json.dumps(response.encode())) + + else: + conn.sendall(json.dumps({"status": "ERROR", "message": "Lệnh không hợp lệ"}).encode('utf-8')) + + except Exception as e: + print(f"[ERROR] Lỗi xử lý client: {e}") + + finally: + conn.close() + print(f"🔴 Kết nối với {addr} đã đóng.") + + def tracker_server(self, host="127.0.0.1", port=5000): + """Khởi động tracker server""" + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((host, port)) + server.listen(5) + print(f"[START] Tracker đang lắng nghe trên {host}:{port}") + + while True: + conn, addr = server.accept() + threading.Thread(target=self.handle_client, args=(conn, addr), daemon=True).start() + + +if __name__ == "__main__": + TRACKER() diff --git a/user.py b/user.py new file mode 100644 index 0000000..5f674fd --- /dev/null +++ b/user.py @@ -0,0 +1,514 @@ +import socket +import threading +import json +import sys + +class USER: + def __init__(self, TRACKER_IP, TRACKER_PORT): + self.TRACKER_IP = TRACKER_IP + self.TRACKER_PORT = TRACKER_PORT + self.name = input("ENTER YOUR NAME: ") + self.ip = input("ENTER YOUR IP (Nhấn Enter để dùng mặc định 127.0.0.1): ") or "127.0.0.1" + self.port = input("ENTER YOUR PORT: ") + + + + #ATTRIBUTE + self.session_id = None + self.response_list = None + self.status_login = None + self.username_login = None + self.all_channelist = None + self.tracker_socket = None + self.isChatRunning = False + + + self.connect_to_tracker() + self.menu() + +# ========== TRACKER TASK ========== + + def connect_to_tracker(self): + """Kết nối và đăng ký với Tracker.""" + try: + self.tracker_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.tracker_socket.connect((self.TRACKER_IP, self.TRACKER_PORT)) + + request = json.dumps({ + "command": "CONNECT", + "name": self.name, + "ip": self.ip, + "port": self.port + }) + self.tracker_socket.sendall(request.encode('utf-8')) + + response = self.tracker_socket.recv(1024).decode('utf-8') + response_data = json.loads(response) + + print(f"[INFO] Tracker response: {response_data.get('message', 'Không có phản hồi')}") + except Exception as e: + print(f"[ERROR] Không thể kết nối đến Tracker: {e}") + self.tracker_socket = None + + def get_peer_list(self): + """Lấy danh sách peer từ Tracker.""" + if self.tracker_socket is None: + print("[ERROR] Chưa kết nối đến tracker.") + return [] + + try: + request = json.dumps({"command": "GET_LIST", "name": self.name}) + self.tracker_socket.sendall(request.encode('utf-8')) + + response = self.tracker_socket.recv(1024).decode('utf-8') + response_data = json.loads(response) + + if response_data.get("status") == "OK": + peer_list = response_data.get("peer_list", []) + print("[INFO] Danh sách peer:") + for peer in peer_list: + print(f" - {peer['name']} ({peer['ip']}:{peer['port']})") + return peer_list + else: + print(f"[ERROR] Tracker trả về lỗi: {response_data.get('message', 'Không có thông báo lỗi')}") + return [] + + except Exception as e: + print(f"[ERROR] Lỗi khi lấy danh sách peer: {e}") + return [] + + def leave_tracker(self): + """Gửi yêu cầu rời khỏi tracker.""" + if self.tracker_socket is None: + print("[ERROR] Chưa kết nối đến tracker.") + return + + try: + request = json.dumps({ + "command": "LEAVE", + "name": self.name, + "ip": self.ip, + "port": self.port + }) + self.tracker_socket.sendall(request.encode('utf-8')) + + response = self.tracker_socket.recv(1024).decode('utf-8') + response_data = json.loads(response) + print(f"[INFO] Tracker response: {response_data.get('message', 'Không có phản hồi')}") + + except Exception as e: + print(f"[ERROR] Lỗi khi rời khỏi tracker: {e}") + + finally: + self.tracker_socket.close() + self.tracker_socket = None + + def send_to_tracker(self, message): + try: + parts = message.split() + command = parts[0].lower() + data = {"command": command} + + if command == "login": + if len(parts) < 3: + return "[ERROR] Cần nhập username và password!" + data["username"] = parts[1] + data["password"] = parts[2] + elif command == "register": + if len(parts) < 4: + return "[ERROR] Cần nhập username, password và email!" + data["username"] = parts[1] + data["password"] = parts[2] + data["email"] = parts[3] + elif command == "visitor": + if len(parts) < 2: + return "[ERROR] Cần nhập tên visitor!" + data["name"] = parts[1] + elif command == "logout": + data["session_id"] = self.session_id + elif command == "create_channel": + data["host"] = parts[1] + data["channel_name"] = parts[2] + elif command == "join_channel": + data["username"] = parts[1] + data["channel_name"] = parts[2] + elif command == "get_user_channels": + data["username"] = parts[1] + elif command == "send_message": + data["username"] = parts[1] + data["channel_name"] = parts[2] + data["message"] = parts[3] + elif command == "get_channel_info": + data["channel_name"] = parts[1] + elif command == "get_all_channels": + pass + elif command == "delete_channel": + data["username"] = parts[1] + data["channel_name"] = parts[2] + elif command == "data": + if len(parts) < 4: + return "[ERROR] Cần nhập IP, Port và tin nhắn!" + data["target_ip"] = parts[1] + data["target_port"] = parts[2] + data["message"] = " ".join(parts[3:]) + + json_message = json.dumps(data) + self.tracker_socket.sendall(json_message.encode('utf-8')) + + response = self.tracker_socket.recv(1024) + if not response: + return "[ERROR] Không nhận được phản hồi từ server." + + response_data = response.decode('utf-8') + if command == "get_user_channels": + response_list = response_data + elif command == "get_all_channels": + all_channelist = response_data + #print(response_list) + #print(type(response_list)) + print(f"[DEBUG] Phản hồi từ Tracker: {response_data}") + #print(type(response_data)) + #print(f"[DEBUG] Dữ liệu nhận được từ server: {response_data}") + + try: + response_dict = json.loads(response_data) + response_dict = json.loads(response_dict) + status_login = response_dict.get("status", {}) + print(status_login) + # print(type(response_dict)) + # print(f"[DEBUG] Phản hồi từ Tracker: {response_dict}") + user_data = response_dict.get("user", {}) + #print("[DEBUG] user_data:", user_data) + sessions = user_data.get("sessions", []) + username_temp = user_data.get("username", {}) + print(username_temp); + #print("[DEBUG] sessions:", sessions) + + if sessions: + self.session_id = sessions[-1]["session_id"] + print("session ID của phiên đăng nhập hiện tại:", self.session_id) + else: + print("Không tìm thấy session nào!") + + except (json.JSONDecodeError, KeyError, IndexError) as e: + print("[LỖI] Không thể lấy session_id:", str(e)) + + + return response_data + + except Exception as e: + return f"[ERROR] Lỗi khi gửi dữ liệu: {e}" + + + + +# ========== CHAT TASK ========== + + def send_message(self, channel_to_send): + """Gửi tin nhắn""" + while self.isChatRunning: + client_input = input("") + if client_input.lower() == "\exit": + self.isChatRunning = False + self.tracker_socket.sendall("OUTCHAT" .encode('utf-8')) + break + #self.leave_tracker() + #os._exit(0) + else: + request = json.dumps({ + "command": "MSG_SEND", + "ip": self.ip, + "port": self.port, + "name": self.name, + "channel": channel_to_send, + "message": client_input + }) + try: + self.tracker_socket.sendall(request.encode('utf-8')) + except Exception as e: + print(f"[ERROR] Lỗi khi gửi tin nhắn: {e}") + break + + def receive_message(self, channel_to_reiceve ): + """Nhận tin nhắn từ server""" + if self.tracker_socket is None: + print("[ERROR] Không có kết nối socket, không thể nhận tin nhắn.") + return + + while self.isChatRunning: + try: + server_message = self.tracker_socket.recv(1024).decode() + if not server_message.strip(): + break + + # Parse JSON từ server + data = json.loads(server_message) + + if data.get("command") == "MSG_RECV": + print(f"\033[1;34m{data['client_name']} >> {data['message']}\033[0m") + + elif data.get("command") == "NOTIFY": + print(f"\033[1;32m[NOTIFY] {data['message']}\033[0m") + + except (json.JSONDecodeError, ConnectionResetError): + print("[ERROR] Mất kết nối với server.") + break + + def talk_to_channel(self, channel_name = "GENERAL" ): + """Bắt đầu chat với server""" + self.isChatRunning = True + threading.Thread(target=self.receive_message, args= channel_name, daemon=True).start() + self.send_message(channel_name) + +# ========== AUTHENTICATION ========== + + def login_or_register(self): + while True: + print("\n=== ĐĂNG NHẬP / ĐĂNG KÝ / VISITOR ===") + print("1. Đăng nhập") + print("2. Đăng ký") + print("3. Vào với tư cách visitor") + print("4. Thoát chương trình") + + choice = input("Chọn: ").strip() + if choice == "1": + username = input("Tên đăng nhập: ").strip() + password = input("Mật khẩu: ").strip() + response = self.send_to_tracker(f"LOGIN {username} {password}") + if username and self.status_login == "success": + self.menu(username) + else: + print("[ERROR] Đăng nhập thất bại, thoát chương trình.") + return username + + elif choice == "2": + username = input("Tên đăng ký: ").strip() + password = input("Mật khẩu: ").strip() + email = input("Email: ").strip() + response = self.send_to_tracker(f"REGISTER {username} {password} {email}") + + elif choice == "3": + visitor_name = input("Tên của bạn: ").strip() + response = self.send_to_tracker(f"VISITOR {visitor_name}") + print("[INFO]", response) + if visitor_name and self.status_login == "success": + self.menu(self.tracker_socket, visitor_name) + else: + print("[ERROR] Đăng nhập thất bại, thoát chương trình.") + #return visitor_name + + elif choice == "4": + print("[INFO] Thoát chương trình.") + sys.exit(0) + else: + print("[ERROR] Vui lòng chọn 1, 2, 3, 4") + + def logout(self): + global session_id + global response_list + print("Session ID của phiên đăng nhập hiện tại:", session_id) + if session_id: + self.send_to_tracker("LOGOUT") + print("[INFO] Đã đăng xuất. Quay lại màn hình đăng nhập...") + session_id = None + response_list = None + else: + print("[ERROR] Bạn chưa đăng nhập hoặc session đã hết hạn!") + + +# ========== MENU TASK ========== + + def menu(self, username): + try: + while True: + print("\n=== MENU ===") + print("1. User channel list") + print("2. Gửi tin nhắn đến peer") + print("3. Create channel") + print("4. Join channel") + print("5. All Channel") + print("6. Đăng xuất") + + choice = input("Chọn một hành động: ").strip() + if choice == "1": + # response_list = json.dumps(response_list) + # print(type(response_list)) + self.send_to_tracker(f"GET_USER_CHANNELS {username}") + #print(response_list) + #print(type(response_list)) + try: + channel_info = json.loads(self.response_list) + #print(channel_info) + #print(type(channel_info)) + joined_channels = channel_info["data"].get("joined_channels", []) + + hosted_channels = channel_info["data"].get("hosted_channels", []) + print("\n=== DANH SÁCH KÊNH ===") + if joined_channels: + print("Joined Channels:") + for idx, channel in enumerate(joined_channels, 1): + print(f"{idx}. {channel}") + else: + print("[INFO] Bạn chưa tham gia kênh nào.") + + if hosted_channels: + print("Hosted Channels:") + for idx, channel in enumerate(hosted_channels, 1): + print(f"{idx}. {channel}") + else: + print("[INFO] Bạn chưa tạo kênh nào.") + + #Select channel + channels = joined_channels + hosted_channels + + selected_channel = input("Nhập tên kênh để vào (hoặc Enter để quay lại): ").strip() + if selected_channel in channels: + print(f"[INFO] Đang vào kênh: {selected_channel}") + print(f"\n=== {selected_channel} ===") + print("1. Channel_Info") + print("2. Delete channel") + print("3. Send Message (Not P2P)") + print("ENTER to back") + option = input("Chon: ").strip() + if option == "1": + self.send_to_tracker(f"GET_CHANNEL_INFO {selected_channel}") + elif option == "2": + self.send_to_tracker(f"DELETE_CHANNEL {username} {selected_channel}") + elif option == "3": + while True: + print("Nhập tin nhắn (Nhấn ENTER để quay lại màn hình trước): ") + text = input("Message: ").strip() + if text == "": + break + else: + self.send_to_tracker(f"SEND_MESSAGE {username} {selected_channel} {text}") + else: + break + else: + print("[ERROR] Tên kênh không hợp lệ.") + except Exception as e: + print(f"[ERROR] Không thể lấy danh sách kênh: {e}") + elif choice == "2": + target_ip = input("Nhập IP của Peer: ").strip() + target_port = input("Nhập Port của Peer: ").strip() + message = input("Nhập tin nhắn: ").strip() + self.send_to_tracker(f"DATA {target_ip} {target_port} {message}") + elif choice == "3": + channel_name = input("Name of channel: ").strip() + host = username + self.send_to_tracker(f"CREATE_CHANNEL {host} {channel_name}") + elif choice == "4": + channel_name = input("Name of channel: ").strip() + self.send_to_tracker(f"JOIN_CHANNEL {username} {channel_name}") + elif choice == "5": + print("\n=== All Channels ===") + + self.send_to_tracker("GET_ALL_CHANNELS") + + try: + channel_info = json.loads(self.all_channelist) + + all_channels = channel_info["data"] + + print("\n=== DANH SÁCH TẤT CẢ CÁC KÊNH ===") + if all_channels: + for idx, channel in enumerate(all_channels, 1): + print(f"{idx}. {channel['channel_name']} (Chủ kênh: {channel['owner']})") + else: + print("[INFO] Hiện tại không có kênh nào.") + + # Chọn kênh để tham gia + selected_channel = input("Nhập tên kênh để vào (hoặc Enter để quay lại): ").strip() + + # Kiểm tra nếu kênh hợp lệ + valid_channels = [c["channel_name"] for c in all_channels] + if selected_channel in valid_channels: + print(f"[INFO] Đang vào kênh: {selected_channel}") + print(f"\n=== {selected_channel} ===") + print("1. Channel_Info") + print("2. Send Message ") + print("ENTER để quay lại") + + option = input("Chọn: ").strip() + + if option == "1": + self.send_to_tracker(f"GET_CHANNEL_INFO {selected_channel}") + elif option == "2": + self.talk_to_channel(selected_channel) + + else: + print("[INFO] Quay lại menu chính.") + + else: + print("[ERROR] Tên kênh không hợp lệ.") + + except Exception as e: + print(f"[ERROR] Không thể lấy danh sách kênh: {e}") + elif choice == "6": + self.logout() + #username = None + self.login_or_register() + else: + print("[ERROR] Vui lòng chọn từ 1 đến 3.") + except KeyboardInterrupt: + print("\n[INFO] Thoát chương trình...") + self.logout(username) + self.sock.close() + sys.exit() + + +if __name__ == '__main__': + USER("127.0.0.1", 5000) + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + def menu(self): + #Hiển thị menu tùy chọn + while True: + print("\n===== MENU =====") + print("0. Thoát") + print("1. Lấy danh sách peer") + print("2. Rời khỏi mạng") + print("3. Gửi tin nhắn") + choice = input("Chọn một tùy chọn: ") + + if choice == "0": + self.leave_tracker() + break + elif choice == "1": + self.get_peer_list() + elif choice == "2": + self.leave_tracker() + break + elif choice == "3": + self.talk_to_server() + else: + print("[ERROR] Lựa chọn không hợp lệ. Hãy nhập lại.") +"""