diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 644a1c2..5d51ac6 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -4,23 +4,37 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.SearchRequestModel; +import org.to.telegramfinalproject.Models.SearchResultModel; import java.io.BufferedReader; +import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; -import static org.to.telegramfinalproject.Database.ChannelDatabase.addSubscriberToChannel; public class ActionHandler { private final PrintWriter out; private final BufferedReader in; private final Scanner scanner; + public static volatile boolean forceExitChat = false; + public static ActionHandler instance; + + private void handleRealTime(JSONObject json) throws IOException { + IncomingMessageListener listener = new IncomingMessageListener(this.in); + listener.handleRealTimeEvent (json); + } public ActionHandler(PrintWriter out, BufferedReader in, Scanner scanner) { this.out = out; this.in = in; this.scanner = scanner; + ActionHandler.instance = this; + } public void loginHandler() { @@ -43,14 +57,41 @@ public class ActionHandler { public void register() { System.out.println("Register form: \n"); - System.out.print("Username: "); - String username = this.scanner.nextLine(); - System.out.print("User id: "); - String user_id = this.scanner.nextLine(); - System.out.print("Password: "); - String password = this.scanner.nextLine(); - System.out.print("Profile name: "); - String profile_name = this.scanner.nextLine(); + + String username, user_id, password, profile_name; + + while (true) { + System.out.print("Username: "); + username = scanner.nextLine().trim(); + if (username.isEmpty()) { + System.out.println("❌ Username cannot be empty."); + continue; + } + + System.out.print("User id: "); + user_id = scanner.nextLine().trim(); + if (user_id.isEmpty()) { + System.out.println("❌ User ID cannot be empty."); + continue; + } + + System.out.print("Password: "); + password = scanner.nextLine(); + if (password.isEmpty()) { + System.out.println("❌ Password cannot be empty."); + continue; + } + + System.out.print("Profile name: "); + profile_name = scanner.nextLine().trim(); + if (profile_name.isEmpty()) { + System.out.println("❌ Profile name cannot be empty."); + continue; + } + + break; + } + JSONObject request = new JSONObject(); request.put("action", "register"); @@ -198,9 +239,9 @@ public class ActionHandler { System.err.println("Error fetching chat info: " + e.getMessage()); } - // اگر شکست خورد، internalId نامعتبر می‌سازیم (برای جلوگیری از null) + //if invalid make unknown modle return new ChatEntry( - UUID.randomUUID(), // ساخت یک UUID موقت (ولی اشتباه) + UUID.randomUUID(), receiverId, "[Unknown " + receiverType + "]", "", @@ -218,9 +259,18 @@ public class ActionHandler { try { JSONObject response = TelegramClient.responseQueue.take(); - if (response != null) { - if (response.getString("status").equals("success")) { - JSONArray chatListJson = response.getJSONObject("data").getJSONArray("chat_list"); + + if (response != null && response.getString("status").equals("success")) { + if (response.has("data") && !response.isNull("data")) { + JSONObject data = response.getJSONObject("data"); + + //is chat list available + if (!data.has("chat_list") || data.isNull("chat_list")) { + System.out.println("❌ chat_list not found in response data."); + return; + } + + JSONArray chatListJson = data.getJSONArray("chat_list"); List chatList = new ArrayList<>(); for (Object obj : chatListJson) { @@ -241,9 +291,15 @@ public class ActionHandler { } Session.chatList = chatList; - System.out.println("✅ Chat list updated."); + System.out.println("✅ Chat list updated. Total: " + chatList.size()); } else { + System.out.println("⚠️ Response has no data object."); + } + } else { + if (response.has("message") && !response.isNull("message")) { System.out.println("❌ Failed to refresh chat list: " + response.getString("message")); + } else { + System.out.println("❌ Failed to refresh chat list."); } } } catch (Exception e) { @@ -253,13 +309,30 @@ public class ActionHandler { } - - public void createGroup() { - System.out.print("Enter group ID: "); - String groupId = scanner.nextLine(); - System.out.print("Enter group name: "); - String groupName = scanner.nextLine(); + String groupId = null; + + while (groupId == null){ + System.out.print("Enter group ID: "); + String input = scanner.nextLine(); + if (!input.trim().isEmpty()) { + groupId = input; + } else { + System.out.println("Group ID can't be empty."); + } + + } + + String groupName = null; + while (groupName == null) { + System.out.print("Enter group name: "); + String input = scanner.nextLine(); + if (!input.trim().isEmpty()) { + groupName = input; + } else { + System.out.println("Group name can't be empty."); + } + } System.out.print("Enter image URL (optional): "); String imageUrl = scanner.nextLine(); @@ -276,10 +349,28 @@ public class ActionHandler { public void createChannel() { - System.out.print("Enter channel ID: "); - String channelId = scanner.nextLine(); - System.out.print("Enter channel name: "); - String channelName = scanner.nextLine(); + String channelId = null; + + while (channelId == null){ + System.out.print("Enter channel ID: "); + String input = scanner.nextLine(); + if (!input.trim().isEmpty()) { + channelId = input; + } else { + System.out.println("Channel ID can't be empty."); + } + + } + String channelName = null; + while (channelName == null) { + System.out.print("Enter channel name: "); + String input = scanner.nextLine(); + if (!input.trim().isEmpty()) { + channelName = input; + } else { + System.out.println("Channel name can't be empty."); + } + } System.out.print("Enter image URL (optional): "); String imageUrl = scanner.nextLine(); @@ -315,19 +406,34 @@ public class ActionHandler { String action = request.getString("action"); this.out.println(request.toString()); - JSONObject response = TelegramClient.responseQueue.take(); + JSONObject response = null; + + while (true) { + JSONObject incoming = TelegramClient.responseQueue.take(); + + if (incoming.has("status")) { + response = incoming; + break; + } else { + //real time + handleRealTime(incoming); + } + } if (response == null) { - System.out.println("⚠️ No response received."); + System.out.println("⚠️ No usable response received."); return; } - System.out.println("✅ Server Response: " + response.getString("message")); + if (response.has("message") && !response.isNull("message")) { + System.out.println("✅ Server Response: " + response.getString("message")); + } - String status = response.getString("status"); + String status = response.optString("status", "error"); if (!"success".equals(status) || !response.has("data") || response.isNull("data")) return; + switch (action) { case "login": case "register": @@ -353,6 +459,7 @@ public class ActionHandler { chatList.add(entry); + } @@ -429,24 +536,33 @@ public class ActionHandler { if (existing != null) { openChat(existing); - } else { - System.out.println("ℹ Trying to add contact..."); - addContact(uuid); + } else { + ChatEntry preview = new ChatEntry(); + preview.setId(String.valueOf(uuid)); + preview.setDisplayId(selected.getString("id")); + preview.setName(selected.getString("name")); + preview.setType("private"); - refreshChatList(); - System.out.println("🔄 Rechecking chat list..."); - - existing = Session.chatList.stream() - .filter(c -> c.getId().equals(uuid) && c.getType().equals("private")) - .findFirst() - .orElse(null); - - if (existing != null) { - openChat(existing); - } else { - System.out.println("❌ Failed to open chat. Try again later."); - } + openForeignChat(preview); } +// else { +// System.out.println("ℹ Trying to add contact..."); +// addContact(uuid); +// +// refreshChatList(); +// System.out.println("🔄 Rechecking chat list..."); +// +// existing = Session.chatList.stream() +// .filter(c -> c.getId().equals(uuid) && c.getType().equals("private")) +// .findFirst() +// .orElse(null); +// +// if (existing != null) { +// openChat(existing); +// } else { +// System.out.println("❌ Failed to open chat. Try again later."); +// } +// } } case "group", "channel" -> { @@ -462,24 +578,33 @@ public class ActionHandler { if (existing != null) { openChat(existing); - } else { - System.out.println("ℹ Trying to join " + type + "..."); - joinGroupOrChannel(type, uuidStr); + }else { + ChatEntry preview = new ChatEntry(); + preview.setId(String.valueOf(uuid)); + preview.setDisplayId(selected.getString("id")); + preview.setName(selected.getString("name")); + preview.setType(type); - refreshChatList(); - System.out.println("🔄 Rechecking chat list..."); - - existing = Session.chatList.stream() - .filter(c -> c.getId().equals(uuid) && c.getType().equals(type)) - .findFirst() - .orElse(null); - - if (existing != null) { - openChat(existing); - } else { - System.out.println("❌ Failed to open " + type + ". Try again later."); - } + openForeignChat(preview); } +// else { +// System.out.println("ℹ Trying to join " + type + "..."); +// joinGroupOrChannel(type, uuidStr); +// +// refreshChatList(); +// System.out.println("🔄 Rechecking chat list..."); +// +// existing = Session.chatList.stream() +// .filter(c -> c.getId().equals(uuid) && c.getType().equals(type)) +// .findFirst() +// .orElse(null); +// +// if (existing != null) { +// openChat(existing); +// } else { +// System.out.println("❌ Failed to open " + type + ". Try again later."); +// } +// } } case "message" -> { @@ -500,6 +625,9 @@ public class ActionHandler { } } + + + break; @@ -558,8 +686,18 @@ public class ActionHandler { } - public void userMenu(UUID internal_uuid) { + public void userMenu(UUID internal_uuid) throws IOException { while (true) { + + + if (Session.backToChatList) { + Session.backToChatList = false; + return; + } + + + + System.out.println("\nUser Menu:"); System.out.println("1. Show chat list"); System.out.println("2. Search"); @@ -570,7 +708,11 @@ public class ActionHandler { String choice = scanner.nextLine(); switch (choice) { - case "1" -> showChatListAndSelect(); + case "1" -> { + Session.inChatListMenu = true; + showChatListAndSelect(); + Session.inChatListMenu = false; + } case "2" -> search(); case "3" -> createChannel(); case "4" -> createGroup(); @@ -584,35 +726,29 @@ public class ActionHandler { } public void showChatListAndSelect() { - if (Session.chatList == null || Session.chatList.isEmpty()) { - System.out.println("No chats available."); + + + List chatList = Session.getChatList(); + if (chatList.isEmpty()) { + System.out.println("📭 You have no chats."); return; } System.out.println("\nYour Chats:"); - for (int i = 0; i < Session.chatList.size(); i++) { - ChatEntry entry = Session.chatList.get(i); - String time = (entry.getLastMessageTime() == null) - ? "No messages yet" - : entry.getLastMessageTime().toString(); - System.out.println((i + 1) + ". [" + entry.getType() + "] " + - entry.getName() + " - Last: " + time); + for (int i = 0; i < chatList.size(); i++) { + ChatEntry entry = chatList.get(i); + String last = entry.getLastMessageTime() == null ? "No messages yet" : entry.getLastMessageTime().toString(); + System.out.printf("%d. [%s] %s - Last: %s\n", i + 1, entry.getType(), entry.getName(), last); } System.out.print("Select a chat by number: "); - int choice = Integer.parseInt(scanner.nextLine()) - 1; - - if(choice == -1){ - System.out.println("Exit..."); - return; - } - if (choice < -1 || choice >= Session.chatList.size()) { - System.out.println("Invalid selection."); + int choice = Integer.parseInt(scanner.nextLine()); + if (choice < 1 || choice > chatList.size()) { + System.out.println("❌ Invalid choice."); return; } - ChatEntry selected = Session.chatList.get(choice); - openChat(selected); + openChat(chatList.get(choice - 1)); } @@ -622,11 +758,48 @@ public class ActionHandler { req.put("action", "get_messages"); req.put("receiver_id", chat.getId()); req.put("receiver_type", chat.getType()); - send(req); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch messages."); + return; + } + + JSONArray messages = res.getJSONObject("data").getJSONArray("messages"); + System.out.println("\n🔓 Messages fetched:"); + System.out.println("─────────────────────────────────────────────"); + for (int i = 0; i < messages.length(); i++) { + JSONObject m = messages.getJSONObject(i); + String senderId = m.getString("sender_id"); + String content = m.getString("content"); + String time = m.getString("send_at"); + + String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other"; + System.out.println("[" + time + "] " + label + ": " + content); + } + System.out.println("─────────────────────────────────────────────"); boolean stayInChat = true; + + Session.currentChatId = chat.getId().toString(); + Session.currentChatEntry = chat; + Session.currentChatType = chat.getType().toLowerCase(); + Session.inChatMenu = true; + startMenuRefresherThread(); + + while (stayInChat) { + if (forceExitChat) { + System.out.println("⚠️ You have been removed from this chat or chat was deleted. Returning to chat list..."); + forceExitChat = false; + break; + } + + System.out.println("\n📍 Entered Chat:"); + System.out.println("🔷 Name: " + chat.getName()); + System.out.println("────────────────────────────────────────────"); + switch (chat.getType().trim().toLowerCase()) { case "private" -> stayInChat = showPrivateChatMenu(chat); case "group" -> stayInChat = showGroupChatMenu(chat); @@ -637,6 +810,12 @@ public class ActionHandler { } } } + + Session.inChatMenu = false; + Session.currentChatId = null; + Session.currentChatEntry = null; + Session.currentChatType = null; + } @@ -644,11 +823,40 @@ public class ActionHandler { private boolean showPrivateChatMenu(ChatEntry chat) { + + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } + + JSONObject req = new JSONObject(); + req.put("action", "view_profile"); + req.put("target_id", chat.getId()); // internal UUID + + JSONObject res = sendWithResponse(req); + + if (res.getString("status").equals("success")) { + JSONObject data = res.getJSONObject("data"); + + System.out.println("\n💬 Private Chat with: " + data.getString("profile_name")); + + if (data.getBoolean("is_online")) { + System.out.println("✅ Status: Online"); + } else { + System.out.println("📅 Last seen: " + data.getString("last_seen")); + } + + System.out.println("────────────────────────────────────────────"); + } + + System.out.println("1. Send message"); System.out.println("2. Block/Unblock"); System.out.println("3. Delete chat (one-sided)"); System.out.println("4. Delete chat (both sides)"); - System.out.println("5. Back"); + System.out.println("5. View profile"); + System.out.println("6. Back"); String input = scanner.nextLine(); @@ -664,6 +872,32 @@ public class ActionHandler { return false; } case "5" -> { + JSONObject reqProfile = new JSONObject(); + reqProfile.put("action", "view_profile"); + reqProfile.put("target_id", chat.getId()); + + JSONObject resProfile = sendWithResponse(reqProfile); + + if (resProfile.getString("status").equals("success")) { + JSONObject profile = resProfile.getJSONObject("data"); + System.out.println("\n👤 Profile Info:"); + System.out.println("🔷 Name: " + profile.optString("profile_name", "Unknown")); + System.out.println("📄 Bio: " + profile.optString("bio", "No bio set")); + System.out.println("🖼️ Image URL: " + profile.optString("image_url", "N/A")); + + if (profile.getBoolean("is_online")) { + System.out.println("✅ Status: Online"); + } else { + System.out.println("📅 Last seen: " + profile.optString("last_seen", "Unknown")); + } + System.out.println("────────────────────────────────────────────"); + } else { + System.out.println("❌ Could not load profile."); + } + return true; + } + + case "6" -> { return false; } default -> System.out.println("Invalid choice."); @@ -686,6 +920,12 @@ public class ActionHandler { private boolean showGroupChatMenu(ChatEntry chat) { + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } + boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); JSONObject perms = getGroupPermissions(chat.getId()); @@ -715,6 +955,11 @@ public class ActionHandler { } else { System.out.println("9. Leave Group"); } + System.out.println("10. View Profile"); + + if (isOwner) { + System.out.println("11. Edit Admin Permissions"); + } System.out.println("0. Back to Chat List"); @@ -750,13 +995,20 @@ public class ActionHandler { case "9" -> { if (isOwner) { transferOwnershipAndLeave(chat.getId()); - refreshChatList(); } else { leaveChat(chat.getId(), "group"); - refreshChatList(); + //refreshChatList(); } return false; } + case "10" ->{ + GroupInfo(chat.getId()); + } + case "11" -> { + if (isOwner) { + editAdminPermissions(chat.getId(), "group"); + } + } case "0" -> { return false; @@ -770,6 +1022,12 @@ public class ActionHandler { private boolean showChannelChatMenu(ChatEntry chat) { + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } + chat = fetchChatInfo(chat.getId().toString(), chat.getType()); boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); @@ -813,6 +1071,11 @@ public class ActionHandler { System.out.println("8. Leave Channel"); } + System.out.println("10. View Profile"); + if (isOwner) { + System.out.println("11. Edit Admin Permissions"); + } + System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); @@ -878,12 +1141,21 @@ public class ActionHandler { case "9" -> { if (isOwner) { transferChannelOwnershipAndLeave(chat.getId()); - refreshChatList(); return false; } else { System.out.println("❌ You don't have permission."); } } + case "10"->{ + ChannelInfo(chat.getId()); + } + case "11" -> { + if (isOwner) { + editAdminPermissions(chat.getId(), "channel"); + } + } + + case "0" -> { return false; } @@ -892,6 +1164,13 @@ public class ActionHandler { return true; } + + + + + + + private void transferOwnershipAndLeave(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_admins"); @@ -910,35 +1189,64 @@ public class ActionHandler { return; } - System.out.println("--- Admins List ---"); - for (int i = 0; i < admins.length(); i++) { - JSONObject admin = admins.getJSONObject(i); - System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + UUID currentUserId = UUID.fromString(Session.getUserUUID()); + boolean success = false; + + while (!success) { + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)%s\n", i + 1, + admin.getString("profile_name"), + admin.getString("user_id"), + admin.getString("user_id").equals(currentUserId.toString()) ? " 👑 (You)" : ""); + } + System.out.println("0. Cancel"); + + System.out.print("Select a new owner by number: "); + String input = scanner.nextLine().trim(); + + if (input.equals("0")) { + System.out.println("❌ Ownership transfer canceled."); + return; + } + + int choice; + try { + choice = Integer.parseInt(input) - 1; + } catch (NumberFormatException e) { + System.out.println("❗ Invalid input. Please enter a number."); + continue; + } + + if (choice < 0 || choice >= admins.length()) { + System.out.println("❗ Invalid selection. Please try again."); + continue; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("user_id"); + + if (newOwnerId.equals(currentUserId.toString())) { + System.out.println("⚠️ You cannot transfer ownership to yourself. Please select a different admin."); + continue; + } + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_group_ownership"); + promoteReq.put("group_id", groupId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + success = true; } - System.out.print("Select a new owner by number: "); - int choice = Integer.parseInt(scanner.nextLine()) - 1; - - if (choice < 0 || choice >= admins.length()) { - System.out.println("Invalid selection."); - return; - } - - JSONObject selected = admins.getJSONObject(choice); - String newOwnerId = selected.getString("user_id"); - - JSONObject promoteReq = new JSONObject(); - promoteReq.put("action", "transfer_group_ownership"); - promoteReq.put("group_id", groupId.toString()); - promoteReq.put("new_owner_user_id", newOwnerId); - - JSONObject promoteRes = sendWithResponse(promoteReq); - if (promoteRes == null || !promoteRes.getString("status").equals("success")) { - System.out.println("❌ Failed to transfer ownership."); - return; - } - - System.out.println("✅ Ownership transferred successfully."); leaveChat(groupId, "group"); } @@ -1078,6 +1386,9 @@ public class ActionHandler { System.out.println(promoteRes.getString("message")); } + + + private void addAdminToGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1114,7 +1425,7 @@ public class ActionHandler { } JSONObject selected = eligible.get(choice); - String targetInternalUUID = selected.getString("internal_uuid"); // دقت کن internal_uuid + String targetInternalUUID = selected.getString("internal_uuid"); JSONObject permissions = new JSONObject(); System.out.print("Can add members? (true/false): "); @@ -1131,7 +1442,7 @@ public class ActionHandler { JSONObject promoteReq = new JSONObject(); promoteReq.put("action", "add_admin_to_group"); promoteReq.put("group_id", groupId.toString()); - promoteReq.put("user_id", targetInternalUUID); // ارسال internal_uuid واقعی + promoteReq.put("user_id", targetInternalUUID); promoteReq.put("permissions", permissions); JSONObject promoteRes = sendWithResponse(promoteReq); @@ -1139,6 +1450,7 @@ public class ActionHandler { System.out.println(promoteRes.getString("message")); } + private void viewGroupMembers(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1163,19 +1475,7 @@ public class ActionHandler { } } - private void removeAdminFromGroup(UUID groupId) { - System.out.print("Enter user_id to remove from admin: "); - String userId = scanner.nextLine().trim(); - JSONObject req = new JSONObject(); - req.put("action", "remove_admin_from_group"); - req.put("group_id", groupId.toString()); - req.put("user_id", userId); - - JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); - } @@ -1253,39 +1553,70 @@ public class ActionHandler { JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); if (admins.length() == 0) { - System.out.println("⚠️ No other admins available. You cannot leave without promoting someone to owner."); + System.out.println("⚠️ No admins available. You cannot leave without transferring ownership."); return; } - System.out.println("\n--- Admins List ---"); - for (int i = 0; i < admins.length(); i++) { - JSONObject admin = admins.getJSONObject(i); - System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + UUID currentUserId = UUID.fromString(Session.getUserUUID()); + boolean success = false; + + while (!success) { + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)%s\n", i + 1, + admin.getString("profile_name"), + admin.getString("user_id"), + admin.getString("internal_uuid").equals(currentUserId.toString()) ? " 👑 (You)" : ""); + } + System.out.println("0. Cancel"); + + System.out.print("Select a new owner by number: "); + String input = scanner.nextLine().trim(); + + if (input.equals("0")) { + System.out.println("❌ Ownership transfer canceled."); + return; + } + + int choice; + try { + choice = Integer.parseInt(input) - 1; + } catch (NumberFormatException e) { + System.out.println("❗ Invalid input. Please enter a number."); + continue; + } + + if (choice < 0 || choice >= admins.length()) { + System.out.println("❗ Invalid selection. Please try again."); + continue; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("internal_uuid"); + + if (newOwnerId.equals(currentUserId.toString())) { + System.out.println("⚠️ You cannot transfer ownership to yourself. Please select a different admin."); + continue; + } + + // Send transfer request + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_channel_ownership"); + promoteReq.put("channel_id", channelId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + success = true; // Break loop } - System.out.print("Select a new owner by number: "); - int choice = Integer.parseInt(scanner.nextLine()) - 1; - - if (choice < 0 || choice >= admins.length()) { - System.out.println("Invalid selection."); - return; - } - - JSONObject selected = admins.getJSONObject(choice); - String newOwnerId = selected.getString("user_id"); - - JSONObject promoteReq = new JSONObject(); - promoteReq.put("action", "transfer_channel_ownership"); - promoteReq.put("channel_id", channelId.toString()); - promoteReq.put("new_owner_user_id", newOwnerId); - - JSONObject promoteRes = sendWithResponse(promoteReq); - if (promoteRes == null || !promoteRes.getString("status").equals("success")) { - System.out.println("❌ Failed to transfer ownership."); - return; - } - - System.out.println("✅ Ownership transferred successfully."); + // Continue execution after successful ownership transfer leaveChat(channelId, "channel"); } @@ -1394,6 +1725,66 @@ public class ActionHandler { } + private void removeAdminFromGroup(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_admins"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + String role = admin.getString("role"); + String profileName = admin.getString("profile_name"); + String userId = admin.getString("user_id"); + + if (!role.equals("owner")) { + eligible.add(admin); + System.out.printf("%d. %s (%s)\n", eligible.size(), profileName, userId); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable admins."); + return; + } + + System.out.print("Select an admin to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("user_id"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_admin_from_group"); + removeReq.put("group_id", groupId.toString()); + removeReq.put("target_user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } + + private void removeAdminFromChannel(UUID channelId) { JSONObject req = new JSONObject(); req.put("action", "view_channel_admins"); @@ -1510,6 +1901,34 @@ public class ActionHandler { } } + private void GroupInfo(UUID groupId){ + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", groupId.toString()); + req.put("receiver_type", "group"); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch group info."); + return; + } + + JSONObject data = res.getJSONObject("data"); + + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); + + System.out.println("\n--- Group Info ---"); + System.out.println("1. Group ID: " + currentId); + System.out.println("2. Name: " + currentName); + System.out.println("3. Description: " + currentDesc); + System.out.println("4. Image URL: " + currentImage); + System.out.println("────────────────────────────────────────────"); + + + } private void editGroupInfo(UUID groupId) { JSONObject req = new JSONObject(); @@ -1609,7 +2028,33 @@ public class ActionHandler { } } + private void ChannelInfo(UUID channelInternalId){ + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", channelInternalId.toString()); + req.put("receiver_type", "channel"); + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch channel info."); + return; + } + + JSONObject data = res.getJSONObject("data"); + + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); + + System.out.println("\n--- Channel Info ---"); + System.out.println("1. Channel ID: " + currentId); + System.out.println("2. Name: " + currentName); + System.out.println("3. Description: " + currentDesc); + System.out.println("4. Image URL: " + currentImage); + System.out.println("────────────────────────────────────────────"); + + } private void editChannelInfo(UUID channelInternalId) { @@ -1690,6 +2135,7 @@ public class ActionHandler { private JSONObject getResponse() { try { return TelegramClient.responseQueue.take(); + } catch (InterruptedException e) { throw new RuntimeException("Failed to get server response"); } @@ -1712,105 +2158,104 @@ public class ActionHandler { - public void processIncomingEvents() { - try { - while (in.ready()) { - String line = in.readLine(); - if (line == null) continue; - - JSONObject response = new JSONObject(line); - if (!response.has("action")) continue; - - String action = response.getString("action"); - - switch (action) { - case "new_message" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n🔔 New Message:"); - System.out.println("From: " + msg.getString("sender")); - System.out.println("Time: " + msg.getString("time")); - System.out.println("Content: " + msg.getString("content")); - System.out.print(">> "); - } - - case "user_status_changed" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n🔄 User Status Changed:"); - System.out.println("User: " + msg.getString("user_id")); - System.out.println("Status: " + msg.getString("status")); - System.out.print(">> "); - } - - case "update_group_or_channel" -> { - JSONObject data = response.getJSONObject("data"); - System.out.println("\n📢 " + data.getString("chat_type") + " updated: " + data.getString("new_name")); - System.out.print(">> "); - } - default -> { - if (!action.equals("search")) { - System.out.println("\n❓ Unknown action received: " + action); - System.out.print(">> "); - } - } - } - } - } catch (Exception e) { - System.out.println("🔴 Failed to process event: " + e.getMessage()); - } - } - public void addAdminToEntity(String type, UUID entityId) { - System.out.print("Enter user ID to promote to admin: "); - String targetUserId = scanner.nextLine().trim(); - - JSONObject permissions = new JSONObject(); - System.out.print("Can send messages? (true/false): "); - permissions.put("can_send", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can edit info? (true/false): "); - permissions.put("can_edit", Boolean.parseBoolean(scanner.nextLine())); - + private void editAdminPermissions(UUID chatId, String chatType) { JSONObject req = new JSONObject(); - req.put("action", type.equals("group") ? "add_admin_to_group" : "add_admin_to_channel"); - req.put(type + "_id", entityId.toString()); - req.put("target_user_id", targetUserId); - req.put("permissions", permissions); - - send(req); - JSONObject res = getResponse(); - System.out.println(res.getString("message")); - } - - - private void editChannelAdminPermissions(UUID channelId) { - System.out.print("Enter user_id of the admin to edit: "); - String userId = scanner.nextLine().trim(); - - JSONObject permissions = new JSONObject(); - System.out.print("Can post? (true/false): "); - permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can edit channel info? (true/false): "); - permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can add members? (true/false): "); - permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can remove members? (true/false): "); - permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can add admins? (true/false): "); - permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can remove admins? (true/false): "); - permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); - - JSONObject req = new JSONObject(); - req.put("action", "edit_channel_admin_permissions"); - req.put("channel_id", channelId.toString()); - req.put("user_id", userId); - req.put("permissions", permissions); + req.put("action", chatType.equals("group") ? "view_group_admins" : "view_channel_admins"); + req.put(chatType + "_id", chatId.toString()); JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + List editableAdmins = new ArrayList<>(); + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + if (!admin.getString("role").equals("owner")) { + editableAdmins.add(admin); + System.out.printf("%d. %s (%s)\n", editableAdmins.size(), + admin.getString("profile_name"), + admin.getString("user_id")); + } + } + + if (editableAdmins.isEmpty()) { + System.out.println("⚠️ No editable admins found."); + return; + } + + System.out.print("Select an admin to edit permissions: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= editableAdmins.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = editableAdmins.get(choice); + String adminId = selected.getString("user_id"); + + JSONObject permissions = new JSONObject(); + + if (chatType.equals("channel")) { + System.out.print("Can post? (true/false): "); + permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can edit channel info? (true/false): "); + permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + } else { + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + + System.out.print("Can edit group info? (true/false): "); + permissions.put("can_edit_group", Boolean.parseBoolean(scanner.nextLine())); + } + + JSONObject updateReq = new JSONObject(); + updateReq.put("action", "edit_admin_permissions"); + updateReq.put("chat_id", chatId.toString()); + updateReq.put("chat_type", chatType); + updateReq.put("admin_id", adminId); + updateReq.put("permissions", permissions); + + JSONObject updateRes = sendWithResponse(updateReq); + if (updateRes != null) + System.out.println(updateRes.getString("message")); } @@ -1852,26 +2297,7 @@ public class ActionHandler { } - public void editAdminPermissions(String type, UUID entityId) { - System.out.print("Enter user ID of the admin to edit: "); - String targetUserId = scanner.nextLine().trim(); - JSONObject permissions = new JSONObject(); - System.out.print("Can send messages? (true/false): "); - permissions.put("can_send", Boolean.parseBoolean(scanner.nextLine())); - System.out.print("Can edit info? (true/false): "); - permissions.put("can_edit", Boolean.parseBoolean(scanner.nextLine())); - - JSONObject req = new JSONObject(); - req.put("action", type.equals("group") ? "edit_group_admin_permissions" : "edit_channel_admin_permissions"); - req.put(type + "_id", entityId.toString()); - req.put("target_user_id", targetUserId); - req.put("permissions", permissions); - - send(req); - JSONObject res = getResponse(); - System.out.println(res.getString("message")); - } public void viewAdmins(String type, UUID entityId) { @@ -1894,17 +2320,25 @@ public class ActionHandler { } - private JSONObject sendWithResponse(JSONObject request) { + public static JSONObject sendWithResponse(JSONObject request) { try { if (!request.has("action") || request.isNull("action")) { System.err.println("❌ Invalid request: missing action."); return null; } - String action = request.getString("action"); - this.out.println(request.toString()); + String requestId = UUID.randomUUID().toString(); + request.put("request_id", requestId); - JSONObject response = TelegramClient.responseQueue.take(); + BlockingQueue queue = new LinkedBlockingQueue<>(); + TelegramClient.pendingResponses.put(requestId, queue); + + TelegramClient.getInstance().getOut().println(request.toString()); + + // Wait for response + JSONObject response = queue.take(); + + TelegramClient.pendingResponses.remove(requestId); if (response == null) { System.out.println("⚠️ No response received."); @@ -1920,4 +2354,366 @@ public class ActionHandler { return null; } } + + + public static void requestChatList() { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_list"); + req.put("user_id", Session.getUserUUID()); + + JSONObject res = sendWithResponse(req); + if (res.getString("status").equals("success")) { + JSONArray chats = res.getJSONObject("data").getJSONArray("chat_list"); + Session.updateChatList(chats); + System.out.println("✅ Chat list updated."); + } else { + System.out.println("❌ Failed to update chat list."); + } + } + + + public static void requestChatInfo(String chatId, String chatType) throws IOException { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", chatId); + req.put("receiver_type", chatType); + TelegramClient.send(req); + } + + + + + public static void displayChatList() { + if (Session.chatList == null || Session.chatList.isEmpty()) { + System.out.println("\n📭 No chats available."); + return; + } + + System.out.println("\n💬 Your Chats:"); + int index = 1; + for (ChatEntry chat : Session.chatList) { + System.out.printf("%d. [%s] %s (%s)\n", index++, chat.getType(), chat.getName(), chat.getDisplayId()); + } + } + + public static class ChatStateMonitor implements Runnable { + private final PrintWriter out; + + public ChatStateMonitor(PrintWriter out) { + this.out = out; + } + + @Override + public void run() { + while (true) { + try { + if (forceExitChat) { + System.out.println("🚪 You were removed from the chat. Returning to chat list..."); + forceExitChat = false; + Session.backToChatList = true; + } + + if (Session.forceRefreshChatList) { + Session.forceRefreshChatList = false; + System.out.println("🔁 Refresh triggered by real-time event."); + ActionHandler.requestChatList(); + if (Session.inChatListMenu) { + System.out.println("\n📬 Updated Chat List:"); + ActionHandler.displayChatList(); //Show chats + System.out.print("Select a chat by number: "); + } + } + + Thread.sleep(300); + } catch (Exception e) { + System.out.println("❌ ChatStateMonitor crashed: " + e.getMessage()); + } + } + } + } + + + public static class CurrentChatMenuRefresher implements Runnable { + private final ActionHandler handler; + + public CurrentChatMenuRefresher(ActionHandler handler) { + this.handler = handler; + } + + @Override + public void run() { + System.out.println("🔁 Menu refresher thread running..."); + + while (true) { + try { + if (Session.refreshCurrentChatMenu && Session.inChatMenu && Session.currentChatId != null) { + System.out.println("🔁 [Refresher] Refresh requested. Searching for chat..."); + + + ChatEntry chat = Session.chatList.stream() + .filter(e -> e.getId().toString().equals(Session.currentChatId)) + .findFirst() + .orElse(null); + + if (chat != null) { + Session.currentChatEntry = chat; + Session.refreshCurrentChatMenu = false; + + String type = chat.getType().toLowerCase(); + System.out.println("\n🔁 Your permissions have changed. Menu updated:"); + + if (type.equals("group")) { + handler.printGroupMenuOnly(chat); + } else if (type.equals("channel")) { + handler.printChannelMenuOnly(chat); + } + + System.out.print("Select an option: "); + } else { + System.out.println("⚠️ No matching chat found for ID: " + Session.currentChatId); + } + } + + Thread.sleep(200); + } catch (Exception e) { + System.out.println("❌ CurrentChatMenuRefresher crashed: " + e.getMessage()); + e.printStackTrace(); + } + } + } + } + + + + + + public void printGroupMenuOnly(ChatEntry chat) { + boolean isAdmin = chat.isAdmin(); + boolean isOwner = chat.isOwner(); + JSONObject perms = getGroupPermissions(chat.getId()); + + System.out.println("\n--- Group Chat Menu (Auto-refreshed) ---"); + System.out.println("1. Send Message"); + System.out.println("2. View Members"); + + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + System.out.println("3. Add Member"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_edit_group", false))) { + System.out.println("4. Edit Group Info"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + System.out.println("5. Add Admin"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + System.out.println("6. Remove Admin"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("7. Remove member"); + } + if (isOwner) { + System.out.println("8. Delete Group"); + System.out.println("9. Leave Group (Transfer ownership required)"); + } else { + System.out.println("9. Leave Group"); + } + + System.out.println("0. Back to Chat List"); + } + + + public void printChannelMenuOnly(ChatEntry chat) { + boolean isAdmin = chat.isAdmin(); + boolean isOwner = chat.isOwner(); + JSONObject perms = getChannelPermissions(chat.getId()); + + System.out.println("\n--- Channel Menu (Auto-refreshed) ---"); + + if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { + System.out.println("1. Send Post"); + } + + if (isOwner || isAdmin) { + System.out.println("2. View Subscribers"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + System.out.println("3. Add Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("4. Remove Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_edit_channel", false))) { + System.out.println("5. Edit Channel Info"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + System.out.println("6. Add Admin"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + System.out.println("7. Remove Admin"); + } + + if (isOwner) { + System.out.println("8. Delete Channel"); + System.out.println("9. Leave Channel (Transfer ownership required)"); + } else { + System.out.println("8. Leave Channel"); + } + + System.out.println("0. Back to Chat List"); + } + + + + + + + private void startMenuRefresherThread() { + Thread refresher = new Thread(() -> { + System.out.println("🟡 Refresher tick. refreshCurrentChatMenu = " + Session.refreshCurrentChatMenu); + + while (true) { + try { + Thread.sleep(500); + if (Session.refreshCurrentChatMenu) { + Session.refreshCurrentChatMenu = false; + if (Session.currentChatEntry == null) { + System.out.println("⚠️ currentChatEntry is null. Skipping..."); + continue; + } + + + switch (Session.currentChatEntry.getType()) { + case "group" -> showGroupChatMenu(Session.currentChatEntry); + case "channel" -> showChannelChatMenu(Session.currentChatEntry); + } + } + } catch (Exception ignored) {} + } + }); + refresher.setDaemon(true); + refresher.start(); + } + + + + private void openForeignChat(ChatEntry chat) { + System.out.println("🔍 Opening " + chat.getType() + " chat (not in your chat list)"); + + JSONObject req = new JSONObject(); + req.put("action", "get_messages"); + req.put("receiver_id", chat.getId().toString()); + req.put("receiver_type", chat.getType()); + send(req); + + switch (chat.getType().toLowerCase()) { + case "private" -> { + System.out.println("\n--- Private Chat ---"); + System.out.println("1. View Profile"); + System.out.println("2. Add to Contact"); + System.out.println("0. Back"); + String input = scanner.nextLine(); + switch (input) { + case "1" ->{ + viewUserProfile(chat.getId()); + openForeignChat(chat); + + } + case "2" -> { + addContact(chat.getId()); + refreshChatList(); + } + default -> System.out.println("Back..."); + } + } + + case "group", "channel" -> { + System.out.println("\n--- " + chat.getType().substring(0, 1).toUpperCase() + chat.getType().substring(1) + " Preview ---"); + System.out.println("1. View Info"); + System.out.println("2. Join " + chat.getType()); + System.out.println("0. Back"); + String input = scanner.nextLine(); + switch (input) { + case "1" -> { + viewGroupOrChannelInfo(chat.getId(), chat.getType()); + openForeignChat(chat); + } + + case "2" -> { + joinGroupOrChannel(chat.getType(), chat.getId().toString()); + refreshChatList(); + ChatEntry joined = Session.chatList.stream() + .filter(c -> c.getId().equals(chat.getId()) && c.getType().equals(chat.getType())) + .findFirst().orElse(null); + if (joined != null) { + openChat(joined); + } else { + System.out.println("❌ Failed to join."); + } + } + default -> System.out.println("Back..."); + } + } + } + } + + + + + + private void viewUserProfile(UUID userId) { + JSONObject req = new JSONObject(); + req.put("action", "view_profile"); + req.put("target_id", userId.toString()); + + JSONObject response = sendWithResponse(req); + + if (response.getString("status").equals("success")) { + JSONObject profile = response.getJSONObject("data"); + System.out.println("\n👤 Profile Info:"); + System.out.println("🔷 Name: " + profile.getString("profile_name")); + System.out.println("🔷User ID: "+profile.getString("user_id")); + System.out.println("📄 Bio: " + profile.optString("bio", "N/A")); + System.out.println("🖼️ Image URL: " + profile.optString("image_url", "N/A")); + if (profile.getBoolean("is_online")) { + System.out.println("✅ Status: Online"); + } else { + System.out.println("📅 Last seen: " + profile.optString("last_seen", "Unknown")); + } + System.out.println("────────────────────────────────────────────"); + } else { + System.out.println("❌ Could not load profile."); + } + } + + private void viewGroupOrChannelInfo(UUID id, String type) { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", id.toString()); + req.put("receiver_type", type); + + JSONObject response = sendWithResponse(req); + + if (response.getString("status").equals("success")) { + JSONObject data = response.getJSONObject("data"); + System.out.println("\n📢 " + type.substring(0, 1).toUpperCase() + type.substring(1) + " Info:"); + System.out.println("🔷 Name: " + data.optString("name", "N/A")); + System.out.println("🆔 ID: " + data.optString("id", "N/A")); + System.out.println("📄 Description: " + data.optString("description", "N/A")); + System.out.println("🖼️ Image: " + data.optString("image_url", "N/A")); + System.out.println("────────────────────────────────────────────"); + } else { + System.out.println("❌ Failed to fetch " + type + " info."); + System.out.println("✅ Server Response: " + response.getString("message")); + } + } + + + } + + diff --git a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java index ff50959..19e24bb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java +++ b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java @@ -1,20 +1,38 @@ package org.to.telegramfinalproject.Client; -public class EventProcessorThread extends Thread { - private final ActionHandler handler; +import org.json.JSONObject; - public EventProcessorThread(ActionHandler handler) { - this.handler = handler; - setDaemon(true); - } +import java.io.BufferedReader; - @Override - public void run() { - while (true) { - try { - Thread.sleep(2000); - handler.processIncomingEvents(); - } catch (InterruptedException ignored) {} - } - } -} +//public class EventProcessorThread extends Thread { +// private final ActionHandler handler; +// private final BufferedReader in; +// +// public EventProcessorThread(ActionHandler handler, BufferedReader in) { +// this.handler = handler; +// this.in = in; +// setDaemon(true); +// } +// +// @Override +// public void run() { +// try { +// System.out.println("👂 Real-Time Listener started."); +// String line; +// while ((line = in.readLine()) != null) { +// JSONObject json = new JSONObject(line); +// System.out.println("📥 Received raw line: " + line); +// +// if (json.has("action")) { +// // پیام real-time +// handler.processIncomingEvent(json); +// } else { +// // پیام پاسخ معمولی +// TelegramClient.responseQueue.put(json); +// } +// } +// } catch (Exception e) { +// System.err.println("❌ Error in EventProcessorThread: " + e.getMessage()); +// } +// } +//} diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 30ca3a0..e10e03e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -1,9 +1,13 @@ package org.to.telegramfinalproject.Client; - import org.json.JSONObject; +import org.to.telegramfinalproject.Models.ChatEntry; import java.io.BufferedReader; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; public class IncomingMessageListener implements Runnable { private final BufferedReader in; @@ -19,25 +23,52 @@ public class IncomingMessageListener implements Runnable { String line; while ((line = in.readLine()) != null) { + + JSONObject response = new JSONObject(line); System.out.println("📥 Received raw line: " + line); + //if it has reqID answer + if (response.has("request_id")) { + String requestId = response.getString("request_id"); + System.out.println("📬 Response with request_id: " + requestId); + System.out.println("📬 Full response: " + response.toString(2)); + + BlockingQueue queue = TelegramClient.pendingResponses.get(requestId); + if (queue != null) { + queue.put(response); + } else { + System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue..."); + TelegramClient.responseQueue.put(response); + } + + continue; + } + + + + //if it has action check it if (response.has("action")) { String action = response.getString("action"); + System.out.println("🎯 [Listener] Action received: " + response.toString(2)); + System.out.println("🎯 Received action: " + action); + if (isRealTimeEvent(action)) { handleRealTimeEvent(response); } else { TelegramClient.responseQueue.put(response); } + } else if (response.has("status") && response.has("message")) { - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // general answer } else { - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // fallback } } } catch (Exception e) { - System.out.println("🔴 Listener stopped: " + e.getMessage()); + System.out.println("🔴 [Listener] Crashed due to: " + e.getMessage()); + e.printStackTrace(); } } @@ -46,15 +77,157 @@ public class IncomingMessageListener implements Runnable { case "new_message", "message_edited", "message_deleted", "user_status_changed", "added_to_group", "added_to_channel", "update_group_or_channel", "chat_deleted", - "blocked_by_user", "unblocked_by_user", "message_seen" -> true; + "blocked_by_user", "unblocked_by_user", "message_seen", + "removed_from_group", "removed_from_channel", + "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> true; default -> false; }; } - private void handleRealTimeEvent(JSONObject response) { + void handleRealTimeEvent(JSONObject response) throws IOException { String action = response.getString("action"); JSONObject msg = response.getJSONObject("data"); + + switch (action) { + case "added_to_group", "added_to_channel", + "removed_from_group", "removed_from_channel", "chat_deleted" -> { + System.out.println("🔄 Chat list changed. Updating..."); + Session.forceRefreshChatList = true; + System.out.println("🧪 Calling requestChatList() after being added"); + + String chatId = msg.getString("chat_id"); + String chatType = msg.getString("chat_type"); + ActionHandler.requestChatInfo(chatId, chatType); + + if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) { + System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting..."); + ActionHandler.forceExitChat = true; + } + } + + case "chat_updated" -> { + System.out.println("\n🔄 Group/Channel info updated."); + new Thread(() -> { + try { + handleAdminRoleChanged(msg); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + case "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> { + System.out.println("🧩 Detected admin/owner role change. Calling handler..."); + new Thread(() -> { + try { + handleAdminRoleChanged(msg); //new thread + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + +// + + + + default -> displayRealTimeMessage(action, msg); + } + + System.out.print(">> "); + } + + private void handleAdminRoleChanged(JSONObject data) throws IOException { + String chatType = data.getString("chat_type"); + String chatId = data.optString("group_id", data.optString("channel_id", data.optString("chat_id", null))); + + if (chatId == null) { + System.out.println("⚠️ No valid ID found in real-time data: " + data.toString(2)); + return; + } + + System.out.println("\n🔄 Your admin status changed. Updating chat info..."); + + try { + // 1. get chat info + JSONObject chatInfoReq = new JSONObject(); + chatInfoReq.put("action", "get_chat_info"); + chatInfoReq.put("receiver_id", chatId); + chatInfoReq.put("receiver_type", chatType); + System.out.println("📤 Sending get_chat_info: " + chatInfoReq); + JSONObject chatInfoResp = ActionHandler.sendWithResponse(chatInfoReq); + JSONObject chatData = chatInfoResp.getJSONObject("data"); + + UUID chatUUID = UUID.fromString(chatData.getString("internal_id")); + + Optional entry = Session.chatList.stream() + .filter(e -> e.getId().equals(chatUUID)) + .findFirst(); + + if (entry.isEmpty()) { + System.out.println("❌ Chat not found in session."); + return; + } + + entry.ifPresent(chat -> { + chat.setAdmin(chatData.optBoolean("is_admin", false)); + chat.setOwner(chatData.optBoolean("is_owner", false)); + chat.setName(chatData.optString("name", "")); + chat.setDisplayId(chatData.optString("id", "")); + chat.setImageUrl(chatData.optString("image_url", "")); + chat.setType(chatData.optString("type", "")); + Session.currentChatEntry = chat; + }); + + // 2. get permission + JSONObject permissionReq = new JSONObject(); + if (chatType.equalsIgnoreCase("group")) { + permissionReq.put("action", "get_group_permissions"); + permissionReq.put("group_id", chatId); + } else { + permissionReq.put("action", "get_channel_permissions"); + permissionReq.put("channel_id", chatId); + } + + JSONObject permissionResp = ActionHandler.sendWithResponse(permissionReq); + JSONObject perm = permissionResp.getJSONObject("data"); + entry.ifPresent(chat -> chat.setPermissions(perm)); + + // 3. set currentChatId + Session.currentChatId = chatUUID.toString(); + + System.out.println("🧪 Checking refresh conditions..."); + System.out.println("🔹 inChatMenu: " + Session.inChatMenu); + System.out.println("🔹 currentChatId: " + Session.currentChatId); + System.out.println("🔹 chatUUID: " + chatUUID); + + if (Session.inChatMenu && Session.currentChatId != null && Session.currentChatId.equals(chatUUID.toString())) { + synchronized (Session.class) { + Session.refreshCurrentChatMenu = true; + } + System.out.println("✅ Admin status updated. Refreshing menu..."); + } else { + System.out.println("❌ Refresh conditions not met."); + } + + } catch (Exception e) { + System.out.println("❌ Exception while handling admin role change: " + e.getMessage()); + e.printStackTrace(); + } + } + + + + + + + + + + + private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { System.out.println("\n🔔 New Message:"); @@ -62,60 +235,36 @@ public class IncomingMessageListener implements Runnable { System.out.println("Time: " + msg.getString("time")); System.out.println("Content: " + msg.getString("content")); } - case "message_edited" -> { System.out.println("\n✏️ Message Edited:"); System.out.println("ID: " + msg.getString("message_id")); System.out.println("New Content: " + msg.getString("new_content")); System.out.println("Edit Time: " + msg.getString("edited_at")); } - case "message_deleted" -> { System.out.println("\n🗑️ Message Deleted:"); System.out.println("Message ID: " + msg.getString("message_id")); } - case "user_status_changed" -> { System.out.println("\n🔄 User Status Changed:"); System.out.println("User: " + msg.getString("user_id")); System.out.println("Status: " + msg.getString("status")); } - - case "added_to_group" -> { - System.out.println("\n👥 You were added to a group: " + msg.getString("chat_name")); - } - - case "added_to_channel" -> { - System.out.println("\n📢 You were added to a channel: " + msg.getString("chat_name")); - } - - case "update_group_or_channel" -> { - System.out.println("\n🔄 Group/Channel updated: " + msg.getString("new_name")); - } - - case "chat_deleted" -> { - System.out.println("\n🗑️ Chat deleted: " + msg.getString("chat_id")); - } - case "blocked_by_user" -> { System.out.println("\n⛔ You were blocked by user: " + msg.getString("blocker_id")); } - case "unblocked_by_user" -> { System.out.println("\n✅ You were unblocked by user: " + msg.getString("unblocker_id")); } - case "message_seen" -> { System.out.println("\n👁️ Your message was seen:"); System.out.println("Message ID: " + msg.getString("message_id")); System.out.println("Seen at: " + msg.getString("seen_at")); } - default -> { System.out.println("\n❓ Unknown real-time action: " + action); + System.out.println(msg.toString(2)); } } - - System.out.print(">> "); } } diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 64270a1..f849e41 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -1,15 +1,30 @@ package org.to.telegramfinalproject.Client; +import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; +import java.util.ArrayList; import java.util.List; +import java.util.UUID; // method for save data from server response public class Session { public static JSONObject currentUser; - public static List chatList; + public static List chatList = new ArrayList<>(); + public static volatile boolean forceRefreshChatList = false; + public static volatile boolean backToChatList = false; + public static boolean inChatListMenu = false; + public static String currentChatType = null; + public static volatile boolean inChatMenu = false; + public static volatile boolean refreshCurrentChatMenu = false; + public static String currentChatId = null; + public static ChatEntry currentChatEntry = null; + + + + public static String getUserUUID() { if (currentUser.has("uuid")) return currentUser.getString("uuid"); @@ -18,4 +33,26 @@ public class Session { throw new RuntimeException("❌ No UUID found in currentUser!"); } + public static void updateChatList(JSONArray chatArray) { + chatList.clear(); + for (int i = 0; i < chatArray.length(); i++) { + JSONObject obj = chatArray.getJSONObject(i); + ChatEntry entry = new ChatEntry( + UUID.fromString(obj.getString("internal_id")), + obj.optString("id", ""), // displayId + obj.optString("name", ""), // name + obj.optString("image_url", ""), + obj.getString("type"), + null, // last message time (if needed, parse it) + obj.optBoolean("is_owner", false), + obj.optBoolean("is_admin", false) + ); + entry.setPermissions(obj.optJSONObject("permissions")); // اگر permissions وجود داره + chatList.add(entry); + } + } + public static List getChatList() { + return chatList; + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 4a32050..8db405e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -7,32 +7,44 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; +import java.util.Map; import java.util.Scanner; import java.util.UUID; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; public class TelegramClient { private static final String SERVER_HOST = "localhost"; private static final int SERVER_PORT = 8000; - private Socket socket; + private static Socket socket; private BufferedReader in; private PrintWriter out; private final Scanner scanner; - ActionHandler handler = null; + private ActionHandler handler; public static BlockingQueue responseQueue = new LinkedBlockingQueue<>(); + public static UUID loggedInUserId = null; + public static final Map> pendingResponses = new ConcurrentHashMap<>(); + + + private static TelegramClient instance; public TelegramClient() { this.scanner = new Scanner(System.in); + instance = this; + } + + public static TelegramClient getInstance() { + return instance; } public void start() { try { - this.socket = new Socket(SERVER_HOST, SERVER_PORT); - this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); - this.out = new PrintWriter(this.socket.getOutputStream(), true); + socket = new Socket(SERVER_HOST, SERVER_PORT); + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out = new PrintWriter(socket.getOutputStream(), true); System.out.println("✅ Connected to Telegram Server"); - this.handler = new ActionHandler(this.out, this.in, this.scanner); + handler = new ActionHandler(out, in, scanner); Thread listenerThread = new Thread(new IncomingMessageListener(in)); listenerThread.setDaemon(true); @@ -45,7 +57,7 @@ public class TelegramClient { } } - private void showMainMenu() { + private void showMainMenu() throws IOException { while (true) { System.out.println("Main Menu:"); System.out.println("1. Register"); @@ -60,7 +72,13 @@ public class TelegramClient { handler.loginHandler(); if (Session.currentUser != null) { System.out.println("✅ Login successful."); + new Thread(new ActionHandler.ChatStateMonitor(out)).start(); +// new Thread(new ActionHandler.CurrentChatMenuRefresher(this.handler)).start(); + + UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); + loggedInUserId = internalId; + handler.userMenu(internalId); } else { System.out.println("❌ Login failed."); @@ -75,7 +93,30 @@ public class TelegramClient { } } + public static void send(JSONObject req) { + try { + responseQueue.clear(); // optional: clear old responses + getInstance().out.println(req.toString()); + System.out.println("📤 [SEND] " + req.toString(2)); + + } catch (Exception e) { + System.err.println("❌ Error sending request: " + e.getMessage()); + } + } + + + + + public static Socket getSocket() { + return socket; + } + public static void main(String[] args) { new TelegramClient().start(); } + + public PrintWriter getOut() { + return out; + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index efa8535..6804ad4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -576,7 +576,7 @@ public class ChannelDatabase { public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) { - String sql = """ + String updateRoles = """ UPDATE channel_subscribers SET role = CASE WHEN user_id = ? THEN 'owner' @@ -586,15 +586,64 @@ public class ChannelDatabase { WHERE channel_id = ? """; + String clearPermissions = """ + UPDATE channel_subscribers + SET permissions = '{}'::jsonb + WHERE channel_id = ? AND user_id = ? + """; + + try (Connection conn = ConnectionDb.connect()) { + conn.setAutoCommit(false); + + try (PreparedStatement roleStmt = conn.prepareStatement(updateRoles); + PreparedStatement clearPermsStmt = conn.prepareStatement(clearPermissions)) { + + roleStmt.setObject(1, newOwnerUUID); + roleStmt.setObject(2, channelId); + roleStmt.executeUpdate(); + + clearPermsStmt.setObject(1, channelId); + clearPermsStmt.setObject(2, newOwnerUUID); + clearPermsStmt.executeUpdate(); + + conn.commit(); + return true; + + } catch (SQLException e) { + conn.rollback(); + e.printStackTrace(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return false; + } + + + public static List getChannelSubscriberUUIDs(UUID channelId) { + List subscriberIds = new ArrayList<>(); try (Connection conn = ConnectionDb.connect(); - PreparedStatement stmt = conn.prepareStatement(sql)) { + PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM channel_subscribers WHERE channel_id = ?")) { + stmt.setObject(1, channelId); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + subscriberIds.add(UUID.fromString(rs.getString("user_id"))); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return subscriberIds; + } - stmt.setObject(1, newOwnerUUID); + public static boolean updateAdminPermissions(UUID channelId, UUID userId, JSONObject permissions) { + String sql = "UPDATE channel_subscribers SET permissions = ?::jsonb WHERE channel_id = ? AND user_id = ? AND role = 'admin'"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, permissions.toString()); stmt.setObject(2, channelId); - - stmt.executeUpdate(); - return true; - + stmt.setObject(3, userId); + return stmt.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; diff --git a/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java b/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java index 8d72664..b58f4c0 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java @@ -7,7 +7,7 @@ import java.sql.SQLException; public class ConnectionDb { private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram"; private static final String USERNAME = "postgres"; - private static final String PASSWORD = "124postpass"; + private static final String PASSWORD = "Partow@1384"; public ConnectionDb() { } diff --git a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index 220f551..edcd83c 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java @@ -34,6 +34,15 @@ public class ContactDatabase { } } + public static List getContactUUIDs(UUID userId) { + List contacts = getContacts(userId); + List contactIds = new ArrayList<>(); + for (Contact c : contacts) { + contactIds.add(c.getContact_id()); + } + return contactIds; + } + public boolean removeContact(UUID user_id, UUID contact_id) { diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 008a147..e64917d 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Database; import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.Group; +import org.to.telegramfinalproject.Models.User; import java.sql.*; import java.time.LocalDateTime; @@ -377,7 +378,7 @@ public class GroupDatabase { } catch (SQLException e) { e.printStackTrace(); } - return "member"; // پیش‌فرض + return "member"; } @@ -444,7 +445,7 @@ public class GroupDatabase { stmt.setObject(2, userId); ResultSet rs = stmt.executeQuery(); - return rs.next(); // اگر رکوردی پیدا شد یعنی owner است + return rs.next(); } catch (SQLException e) { e.printStackTrace(); } @@ -460,7 +461,7 @@ public class GroupDatabase { ResultSet rs = stmt.executeQuery(); if (rs.next()) { String role = rs.getString("role"); - return "admin".equals(role) || "owner".equals(role); // owner هم admin هست + return "admin".equals(role) || "owner".equals(role); } } catch (SQLException e) { e.printStackTrace(); @@ -508,7 +509,7 @@ public class GroupDatabase { while (rs.next()) { JSONObject member = new JSONObject(); member.put("profile_name", rs.getString("profile_name")); - member.put("user_id", rs.getString("user_id")); // آیدی قابل نمایش + member.put("user_id", rs.getString("user_id")); member.put("internal_uuid", rs.getObject("internal_uuid").toString()); member.put("role", rs.getString("role")); @@ -556,7 +557,7 @@ public class GroupDatabase { public static boolean transferOwnership(UUID groupId, UUID newOwnerId) { String demoteOldOwner = "UPDATE group_members SET role = 'admin' WHERE group_id = ? AND role = 'owner'"; - String promoteNewOwner = "UPDATE group_members SET role = 'owner' WHERE group_id = ? AND user_id = ?"; + String promoteNewOwner = "UPDATE group_members SET role = 'owner', permissions = '{}'::jsonb WHERE group_id = ? AND user_id = ?"; try (Connection conn = ConnectionDb.connect()) { conn.setAutoCommit(false); @@ -600,4 +601,82 @@ public class GroupDatabase { } + public static List getGroupMemberUUIDs(UUID groupId) { + List memberIds = new ArrayList<>(); + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM group_members WHERE group_id = ?")) { + stmt.setObject(1, groupId); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + memberIds.add(UUID.fromString(rs.getString("user_id"))); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return memberIds; + } + + public static boolean updateAdminPermissions(UUID groupId, UUID userId, JSONObject permissions) { + String sql = "UPDATE group_members SET permissions = ?::jsonb WHERE group_id = ? AND user_id = ? AND role = 'admin'"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, permissions.toString()); + stmt.setObject(2, groupId); + stmt.setObject(3, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean isMember(UUID groupId, UUID userId) { + String sql = "SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + +// public static List searchGroupMembers(UUID groupId, String keyword) { +// List result = new ArrayList<>(); +// String sql = """ +// SELECT u.* +// FROM users u +// JOIN group_members gm ON gm.user_id = u.internal_uuid +// WHERE gm.group_id = ? +// AND ( +// LOWER(u.profile_name) LIKE ? +// OR LOWER(u.username) LIKE ? +// OR LOWER(u.user_id) LIKE ? +// ) +// """; +// +// try (Connection conn = ConnectionDb.connect(); +// PreparedStatement stmt = conn.prepareStatement(sql)) { +// +// stmt.setObject(1, groupId); +// String likePattern = "%" + keyword.toLowerCase() + "%"; +// stmt.setString(2, likePattern); +// stmt.setString(3, likePattern); +// stmt.setString(4, likePattern); +// +// ResultSet rs = stmt.executeQuery(); +// while (rs.next()) { +// User user = User.fromResultSet(rs); +// result.add(user); +// } +// +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// return result; +// } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index a330da4..6064500 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -9,13 +9,58 @@ import java.util.List; import java.util.UUID; public class userDatabase { + + public userDatabase() { } - private Connection getConnection() throws SQLException { + public static boolean isUserOnline(UUID userId) { + String sql = "SELECT status FROM users WHERE internal_uuid = ?"; + + try (Connection conn = getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + String status = rs.getString("status"); + return "online".equalsIgnoreCase(status); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return false; + } + + + + private static Connection getConnection() throws SQLException { return ConnectionDb.connect(); } + public static String getLastSeen(UUID userId) { + String sql = "SELECT last_seen FROM users WHERE internal_uuid = ?"; + + try (Connection conn = getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + Timestamp lastSeen = rs.getTimestamp("last_seen"); + if (lastSeen != null) { + return lastSeen.toLocalDateTime().toString(); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return "Unknown"; + } + + public User findByUserId(String userId) { String query = "SELECT * FROM users WHERE user_id = ?"; diff --git a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java index cbd0940..7491297 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java @@ -37,6 +37,10 @@ public class ChatEntry { } + public ChatEntry() { + + } + // 🟩 گتر و ستر جدید public boolean isOwner() { return isOwner; @@ -54,7 +58,6 @@ public class ChatEntry { isAdmin = admin; } - // سایر گترها public UUID getId() { return internalId; } @@ -87,4 +90,22 @@ public class ChatEntry { public void setPermissions(JSONObject permissions) { this.permissions = permissions; } + + public void setName(String name) {this.name = name; + } + + public void setDisplayId(String id) {this.displayId = id; + } + + public void setImageUrl(String image_url) {this.imageUrl = image_url; + } + + public void setType(String type) {this.type =type; + } + + public void setId(String internalId) {this.internalId = UUID.fromString(internalId); + } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java b/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java index 7434561..f127dab 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ResponseModel.java @@ -6,6 +6,8 @@ public class ResponseModel { private String status; private String message; private JSONObject data; + private String requestId; + public ResponseModel(String status, String message) { @@ -29,4 +31,20 @@ public class ResponseModel { } public JSONObject getData() {return this.data;} + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + json.put("status", this.status); + json.put("message", this.message); + json.put("data", this.data != null ? this.data : JSONObject.NULL); + if (this.requestId != null) { + json.put("request_id", this.requestId); + } + return json; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 76022b6..e76ce67 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -79,6 +79,10 @@ public class ClientHandler implements Runnable { SessionManager.addUser(user.getInternal_uuid(), this.socket); userDatabase.updateUserStatus(user.getInternal_uuid(), "online"); + //RealTime + List contactIds = ContactDatabase.getContactUUIDs(user.getInternal_uuid()); + RealTimeEventDispatcher.notifyUserStatusChanged(user.getInternal_uuid(), "online", contactIds); + List contacts = ContactDatabase.getContacts(user.getInternal_uuid()); List groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid()); List channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid()); @@ -158,10 +162,17 @@ public class ClientHandler implements Runnable { String user_Id = requestJson.optString("user_id"); if (user_Id != null && !user_Id.isEmpty()) { try { - UUID uuid = UUID.fromString(user_Id); - userDatabase.updateUserStatus(uuid, "offline"); - userDatabase.updateLastSeen(uuid); - SessionManager.removeUser(uuid); + userId = UUID.fromString(user_Id); + + userDatabase.updateUserStatus(userId, "offline"); + + // Real-Time + List contacts = ContactDatabase.getContactUUIDs(userId); + RealTimeEventDispatcher.notifyUserStatusChanged(userId, "offline", contacts); + + userDatabase.updateLastSeen(userId); + SessionManager.removeUser(userId); + response = new ResponseModel("success", "Logged out."); } catch (IllegalArgumentException e) { response = new ResponseModel("error", "Invalid UUID format."); @@ -178,6 +189,10 @@ public class ClientHandler implements Runnable { List results = new ArrayList<>(); String user_Id = requestJson.getString("user_id"); User currentUser = new userDatabase().findByUserId(user_Id); + if (currentUser == null) { + response = new ResponseModel("error", "User not found or not logged in."); + break; + } UUID currentUserUUID = currentUser.getInternal_uuid(); for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { @@ -230,10 +245,12 @@ public class ClientHandler implements Runnable { } case "search": { + String keyword = requestJson.optString("keyword"); List results = new ArrayList<>(); String user_Id = requestJson.getString("user_id"); User currentUser = new userDatabase().findByUserId(user_Id); + UUID currentUserUUID = currentUser.getInternal_uuid(); for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { @@ -316,10 +333,27 @@ public class ClientHandler implements Runnable { } case "add_contact": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); UUID contactUUID = UUID.fromString(requestJson.getString("contact_id")); boolean success = ContactDatabase.addContact(userUUID, contactUUID); + + //RealTime + if (success) { + RealTimeEventDispatcher.notifyAddedToChat( + "private", + userUUID, + currentUser.getProfile_name(), + currentUser.getImage_url(), + contactUUID + ); + } + response = success ? new ResponseModel("success", "Contact added successfully.") : new ResponseModel("error", "Failed to add contact. Maybe already exists."); @@ -327,6 +361,10 @@ public class ClientHandler implements Runnable { } case "join_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID userUUID = UUID.fromString(requestJson.getString("user_id")); Group group = GroupDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); if (group == null) { @@ -336,6 +374,13 @@ public class ClientHandler implements Runnable { boolean joined = GroupDatabase.addMemberToGroup(userUUID, group.getInternal_uuid()); + + //RealTime + if (joined) { + List members = GroupDatabase.getMemberUUIDs(group.getInternal_uuid()); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("group", group.getInternal_uuid(), group.getGroup_name(), group.getImage_url(), group.getDescription(), members); + } + response = joined ? new ResponseModel("success", "Joined group.") : new ResponseModel("error", "Failed to join group."); @@ -343,6 +388,10 @@ public class ClientHandler implements Runnable { } case "join_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID userUUID = UUID.fromString(requestJson.getString("user_id")); Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); if (channel == null) { @@ -352,6 +401,13 @@ public class ClientHandler implements Runnable { boolean joined = ChannelDatabase.addSubscriberToChannel(userUUID, channel.getInternal_uuid()); + + //RealTime + if (joined) { + List members = ChannelDatabase.getChannelSubscriberUUIDs(channel.getInternal_uuid()); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("channel", channel.getInternal_uuid(), channel.getChannel_name(), channel.getImage_url(), channel.getDescription(), members); + } + response = joined ? new ResponseModel("success", "Joined channel.") : new ResponseModel("error", "Failed to join channel."); @@ -360,6 +416,8 @@ public class ClientHandler implements Runnable { case "get_chat_info": { + + try { String id = requestJson.getString("receiver_id"); String type = requestJson.getString("receiver_type"); @@ -452,22 +510,26 @@ public class ClientHandler implements Runnable { case "get_chat_list": { - String userIdStr = requestJson.getString("user_id"); - User user = new userDatabase().findByUserId(userIdStr); + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } - List contacts = ContactDatabase.getContacts(user.getInternal_uuid()); - List groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid()); - List channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid()); + List contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid()); + List groups = GroupDatabase.getGroupsByUser(currentUser.getInternal_uuid()); + List channels = ChannelDatabase.getChannelsByUser(currentUser.getInternal_uuid()); List chatList = new ArrayList<>(); + for (Contact contact : contacts) { User target = userDatabase.findByInternalUUID(contact.getContact_id()); if (target == null) continue; - LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(user.getInternal_uuid(), target.getInternal_uuid(), "private"); + + LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(currentUser.getInternal_uuid(), target.getInternal_uuid(), "private"); chatList.add(new ChatEntry( - target.getInternal_uuid(), // internal UUID - target.getUser_id(), // public display ID + target.getInternal_uuid(), + target.getUser_id(), target.getProfile_name(), target.getImage_url(), "private", @@ -479,8 +541,8 @@ public class ClientHandler implements Runnable { for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); - boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), user.getInternal_uuid()); - boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), user.getInternal_uuid()); + boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), currentUser.getInternal_uuid()); + boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), currentUser.getInternal_uuid()); chatList.add(new ChatEntry( group.getInternal_uuid(), @@ -496,8 +558,8 @@ public class ClientHandler implements Runnable { for (Channel channel : channels) { LocalDateTime last = MessageDatabase.getLastMessageTime(channel.getInternal_uuid(), "channel"); - boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), user.getInternal_uuid()); - boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), user.getInternal_uuid()); + boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), currentUser.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), currentUser.getInternal_uuid()); chatList.add(new ChatEntry( channel.getInternal_uuid(), @@ -511,7 +573,6 @@ public class ClientHandler implements Runnable { )); } - chatList.sort((a, b) -> { if (a.getLastMessageTime() == null) return 1; if (b.getLastMessageTime() == null) return -1; @@ -520,14 +581,19 @@ public class ClientHandler implements Runnable { JSONObject data = new JSONObject(); data.put("chat_list", JsonUtil.chatListToJson(chatList)); - response = new ResponseModel("success", "Chat list updated.", data); + break; } + case "create_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { String groupId = requestJson.getString("group_id"); String groupName = requestJson.getString("group_name"); @@ -563,6 +629,10 @@ public class ClientHandler implements Runnable { case "create_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { String channelId = requestJson.getString("channel_id"); String channelName = requestJson.getString("channel_name"); @@ -598,6 +668,10 @@ public class ClientHandler implements Runnable { } case "add_admin_to_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); @@ -620,6 +694,20 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); + + //RealTime + if (success) { + Channel channel = ChannelDatabase.findByInternalUUID(channelId); + if (channel != null) { + RealTimeEventDispatcher.notifyBecameAdmin( + "channel", + channel.getInternal_uuid(), + channel.getChannel_name(), + channel.getImage_url(), + targetUserId + ); + } + } response = success ? new ResponseModel("success", "Admin added to channel.") : new ResponseModel("error", "Failed to add admin."); @@ -630,6 +718,10 @@ public class ClientHandler implements Runnable { } case "edit_channel_admin_permissions": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); JSONObject permissions = requestJson.optJSONObject("permissions"); @@ -649,6 +741,10 @@ public class ClientHandler implements Runnable { case "edit_group_info": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { UUID groupUUID = UUID.fromString(requestJson.getString("group_id")); // internal_uuid String newGroupId = requestJson.getString("new_group_id").trim(); // شناسه نمایشی جدید @@ -673,9 +769,11 @@ public class ClientHandler implements Runnable { ? new ResponseModel("success", "Group info updated successfully.") : new ResponseModel("error", "Failed to update group info."); - //if (updated) { - //RealTimeEventDispatcher.sendGroupOrChannelUpdate(groupUUID, "group", name); - //} + //RealTime + if (updated) { + List members = GroupDatabase.getMemberUUIDs(groupUUID); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("group", groupUUID, name, imageUrl, description, members); + } } catch (Exception e) { response = new ResponseModel("error", "Error updating group: " + e.getMessage()); @@ -687,12 +785,12 @@ public class ClientHandler implements Runnable { case "view_channel_admins": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); - //if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { - // response = new ResponseModel("error", "You are not allowed to add admins to the channel."); - //break; - //} List admins = ChannelDatabase.getChannelAdminsAndOwner(channelId); @@ -705,6 +803,10 @@ public class ClientHandler implements Runnable { case "add_admin_to_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); JSONObject permissions = requestJson.optJSONObject("permissions"); @@ -716,6 +818,20 @@ public class ClientHandler implements Runnable { boolean success = GroupDatabase.addAdminToGroup(groupId, targetUserId, permissions); + + //RealTime + if (success) { + Group group = GroupDatabase.findByInternalUUID(groupId); + if (group != null) { + RealTimeEventDispatcher.notifyBecameAdmin( + "group", + group.getInternal_uuid(), + group.getGroup_name(), + group.getImage_url(), + targetUserId + ); + } + } response = success ? new ResponseModel("success", "Admin added to group.") : new ResponseModel("error", "Failed to add admin."); @@ -724,6 +840,10 @@ public class ClientHandler implements Runnable { case "edit_group_admin_permissions": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); JSONObject permissions = requestJson.optJSONObject("permissions"); @@ -745,6 +865,10 @@ public class ClientHandler implements Runnable { case "remove_admin_from_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserUUID = UUID.fromString(requestJson.getString("target_user_id")); @@ -764,6 +888,21 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.demoteAdminToSubscriber(channelId, targetUserUUID); + + //RealTime + if (success) { + Channel channel = ChannelDatabase.findByInternalUUID(channelId); + if (channel != null) { + RealTimeEventDispatcher.notifyRemovedAdminFromChat( + "channel", + channel.getInternal_uuid(), + channel.getChannel_name(), + channel.getImage_url(), + targetUserUUID + ); + } + } + response = success ? new ResponseModel("success", "Admin removed successfully.") : new ResponseModel("error", "Failed to remove admin."); @@ -772,6 +911,10 @@ public class ClientHandler implements Runnable { case "add_member_to_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -786,14 +929,33 @@ public class ClientHandler implements Runnable { } boolean success = GroupDatabase.addMemberToGroup(targetUserId, groupId); + + Group group = GroupDatabase.findByInternalUUID(groupId); + + //RealTime + if (success && group != null) { + RealTimeEventDispatcher.notifyAddedToChat( + "group", + group.getInternal_uuid(), + group.getGroup_name(), + group.getImage_url(), + targetUserId + ); + } + response = success ? new ResponseModel("success", "Member added to group.") : new ResponseModel("error", "Failed to add member."); break; + } case "remove_member_from_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -813,7 +975,18 @@ public class ClientHandler implements Runnable { break; } + //RealTime boolean success = GroupDatabase.removeMemberFromGroup(groupId, targetUserId); + if (success) { + Group group = GroupDatabase.findByInternalUUID(groupId); + if (group != null) { + RealTimeEventDispatcher.notifyRemovedFromChat( + "group", + group.getInternal_uuid(), + targetUserId + ); + } + } response = success ? new ResponseModel("success", "Member removed from group.") : new ResponseModel("error", "Failed to remove member."); @@ -823,6 +996,10 @@ public class ClientHandler implements Runnable { case "view_group_admins": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); String role = GroupDatabase.getGroupRole(groupId, currentUser.getInternal_uuid()); @@ -840,6 +1017,10 @@ public class ClientHandler implements Runnable { case "get_messages": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { String receiverId = requestJson.getString("receiver_id"); String receiverType = requestJson.getString("receiver_type"); @@ -905,12 +1086,24 @@ public class ClientHandler implements Runnable { } case "toggle_block": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { UUID userUUID = UUID.fromString(requestJson.getString("user_id")); UUID targetUUID = UUID.fromString(requestJson.getString("target_id")); boolean isBlocked = ContactDatabase.toggleBlock(userUUID, targetUUID); + //RealTime + if (isBlocked) { + RealTimeEventDispatcher.notifyBlocked(userUUID, targetUUID); + } else { + RealTimeEventDispatcher.notifyUnblocked(userUUID, targetUUID); + } + + String message = isBlocked ? "🔒 User blocked successfully." : "🔓 User unblocked successfully."; response = new ResponseModel("success", message); @@ -923,9 +1116,12 @@ public class ClientHandler implements Runnable { case "transfer_group_ownership": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID newOwnerUserId = UUID.fromString(requestJson.getString("new_owner_user_id")); - User newOwner = userDatabase.findByInternalUUID(newOwnerUserId); if (newOwner == null) { response = new ResponseModel("error", "New owner not found."); @@ -944,6 +1140,33 @@ public class ClientHandler implements Runnable { boolean success = GroupDatabase.transferOwnership(groupId, newOwner.getInternal_uuid()); + //RealTime + if (success) { + List members = GroupDatabase.getMemberUUIDs(groupId); + Group group = GroupDatabase.findByInternalUUID(groupId); + + //update for new owner + RealTimeEventDispatcher.sendOwnershipTransferred( + "group", + groupId, + group.getGroup_name(), + List.of(newOwner.getInternal_uuid()) + ); + + //Update for everyone + RealTimeEventDispatcher.sendGroupOrChannelUpdate( + "group", + groupId, + group.getGroup_name(), + group.getImage_url(), + group.getDescription(), + members + ); + } + + + + response = success ? new ResponseModel("success", "Ownership transferred.") : new ResponseModel("error", "Failed to transfer ownership."); @@ -953,6 +1176,10 @@ public class ClientHandler implements Runnable { case "view_group_members" : { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); JSONArray members = GroupDatabase.getGroupMembers(groupId); @@ -970,14 +1197,30 @@ public class ClientHandler implements Runnable { case "delete_private_chat" : { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID targetId = UUID.fromString(requestJson.getString("target_id")); boolean both = requestJson.getBoolean("both"); + + //RealTime + if (both) { + RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid())); + RealTimeEventDispatcher.notifyChatDeleted("private", currentUser.getInternal_uuid(), List.of(targetId)); + } else { + RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid())); + } response = PrivateChatService.deletePrivateChat(currentUser.getInternal_uuid(), targetId, both); break; } case "get_group_permissions": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); userId = currentUser.getInternal_uuid(); @@ -989,12 +1232,19 @@ public class ClientHandler implements Runnable { case "leave_chat": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } String chatType = requestJson.getString("chat_type"); UUID chatId = UUID.fromString(requestJson.getString("chat_id")); userId = UUID.fromString(requestJson.getString("user_id")); boolean success = false; + + + switch (chatType) { case "group" -> success = GroupDatabase.removeMemberFromGroup(chatId, userId); case "channel" -> success = ChannelDatabase.removeSubscriberFromChannel(chatId, userId); @@ -1004,6 +1254,15 @@ public class ClientHandler implements Runnable { } } + //RealTime + if (success) { + if (chatType.equals("group")) { + RealTimeEventDispatcher.notifyRemovedFromChat("group", chatId, userId); + } else if (chatType.equals("channel")) { + RealTimeEventDispatcher.notifyRemovedFromChat("channel", chatId, userId); + } + } + if (response == null) { response = success ? new ResponseModel("success", "Left the " + chatType + " successfully.") @@ -1015,6 +1274,10 @@ public class ClientHandler implements Runnable { case "delete_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID groupId = UUID.fromString(requestJson.getString("group_id")); if (!GroupDatabase.isOwner(groupId, currentUser.getInternal_uuid())) { @@ -1022,16 +1285,26 @@ public class ClientHandler implements Runnable { break; } + List memberIds = GroupDatabase.getGroupMemberUUIDs(groupId); + boolean success = GroupDatabase.deleteGroup(groupId); - response = success - ? new ResponseModel("success", "Group deleted successfully.") - : new ResponseModel("error", "Failed to delete group."); + //RealTime + if (success) { + RealTimeEventDispatcher.notifyChatDeleted("group", groupId, memberIds); + response = new ResponseModel("success", "Group deleted successfully."); + } else { + response = new ResponseModel("error", "Failed to delete group."); + } break; } case "get_channel_permissions": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); userId = currentUser.getInternal_uuid(); @@ -1047,6 +1320,10 @@ public class ClientHandler implements Runnable { case "view_channel_subscribers": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); boolean isOwner = ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid()); @@ -1071,6 +1348,10 @@ public class ClientHandler implements Runnable { case "add_subscriber_to_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -1085,6 +1366,20 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.addSubscriberToChannel(targetUserId, channelId); + + //RealTime + if (success) { + Channel channel = ChannelDatabase.findByInternalUUID(channelId); + if (channel != null) { + RealTimeEventDispatcher.notifyAddedToChat( + "channel", + channel.getInternal_uuid(), + channel.getChannel_name(), + channel.getImage_url(), + targetUserId + ); + } + } response = success ? new ResponseModel("success", "Subscriber added to channel.") : new ResponseModel("error", "Failed to add subscriber."); @@ -1092,6 +1387,10 @@ public class ClientHandler implements Runnable { } case "remove_subscriber_from_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -1112,6 +1411,18 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.removeSubscriberFromChannel(channelId, targetUserId); + + //RealTime + if (success) { + Channel channel = ChannelDatabase.findByInternalUUID(channelId); + if (channel != null) { + RealTimeEventDispatcher.notifyRemovedFromChat( + "channel", + channel.getInternal_uuid(), + targetUserId + ); + } + } response = success ? new ResponseModel("success", "Subscriber removed from channel.") : new ResponseModel("error", "Failed to remove subscriber."); @@ -1120,6 +1431,10 @@ public class ClientHandler implements Runnable { case "edit_channel_info": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } try { UUID channelUUID = UUID.fromString(requestJson.getString("channel_id")); // internal_uuid String newChannelId = requestJson.getString("new_channel_id").trim(); @@ -1140,6 +1455,13 @@ public class ClientHandler implements Runnable { } boolean updated = ChannelDatabase.updateChannelInfo(channelUUID, newChannelId, name, description, imageUrl); + + //RealTime + if (updated) { + List subscribers = ChannelDatabase.getChannelSubscriberUUIDs(channelUUID); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("channel", channelUUID, name, imageUrl, description, subscribers); + } + response = updated ? new ResponseModel("success", "Channel info updated successfully.") : new ResponseModel("error", "Failed to update channel info."); @@ -1152,6 +1474,10 @@ public class ClientHandler implements Runnable { case "delete_channel": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); if (!ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid())) { @@ -1159,15 +1485,80 @@ public class ClientHandler implements Runnable { break; } + List subscriberIds = ChannelDatabase.getChannelSubscriberUUIDs(channelId); + boolean success = ChannelDatabase.deleteChannel(channelId); - response = success - ? new ResponseModel("success", "Channel deleted successfully.") - : new ResponseModel("error", "Failed to delete channel."); + + //RealTime + if (success) { + RealTimeEventDispatcher.notifyChatDeleted("channel", channelId, subscriberIds); + response = new ResponseModel("success", "Channel deleted successfully."); + } else { + response = new ResponseModel("error", "Failed to delete channel."); + } break; } + case "remove_admin_from_group": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + + if (!GroupPermissionUtil.canRemoveAdmins(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove admins."); + break; + } + + String targetRole = GroupDatabase.getGroupRole(groupId, targetUserId); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a member of the group."); + break; + } + + if (targetRole.equals("owner")) { + response = new ResponseModel("error", "You cannot remove the owner."); + break; + } + + if (!targetRole.equals("admin")) { + response = new ResponseModel("error", "Target user is not an admin."); + break; + } + + boolean success = GroupDatabase.demoteAdminToMember(groupId, targetUserId); + + // RealTime + if (success) { + Group group = GroupDatabase.findByInternalUUID(groupId); + if (group != null) { + RealTimeEventDispatcher.notifyRemovedAdminFromChat( + "group", + group.getInternal_uuid(), + group.getGroup_name(), + group.getImage_url(), + targetUserId + ); + } + } + + response = success + ? new ResponseModel("success", "Admin removed successfully.") + : new ResponseModel("error", "Failed to remove admin."); + + break; + } + + + case "transfer_channel_ownership": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } UUID channelId = UUID.fromString(requestJson.getString("channel_id")); String newOwnerUserIdStr = requestJson.getString("new_owner_user_id"); @@ -1183,23 +1574,164 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.transferOwnership(channelId, newOwner.getInternal_uuid()); + + //RealTime + if (success) { + List subscribers = ChannelDatabase.getChannelSubscriberUUIDs(channelId); + Channel channel = ChannelDatabase.findByInternalUUID(channelId); // 🟢 گرفتن اطلاعات کامل + + // 1. ارسال ownership_transferred فقط به owner جدید + RealTimeEventDispatcher.sendOwnershipTransferred( + "channel", + channelId, + channel.getChannel_name(), // ✅ اسم واقعی کانال + List.of(newOwner.getInternal_uuid()) + ); + + // 2. ارسال update_group_or_channel برای بروزرسانی UI تمام افراد + RealTimeEventDispatcher.sendGroupOrChannelUpdate( + "channel", + channelId, + channel.getChannel_name(), // ✅ name واقعی + channel.getImage_url(), // ✅ image + channel.getDescription(), // ✅ description + subscribers + ); + } + + + response = success + ? new ResponseModel("success", "Ownership transferred successfully.") : new ResponseModel("error", "Failed to transfer ownership."); break; } + case "view_profile": { + UUID targetId = UUID.fromString(requestJson.getString("target_id")); + User user = userDatabase.findByInternalUUID(targetId); + if (user == null) { + response = new ResponseModel("error", "User not found."); + break; + } + + JSONObject data = new JSONObject(); + data.put("profile_name", user.getProfile_name()); + data.put("user_id", user.getUser_id()); + data.put("bio", user.getBio()); + data.put("image_url", user.getImage_url()); + data.put("is_online", userDatabase.isUserOnline(user.getInternal_uuid())); + data.put("last_seen", userDatabase.getLastSeen(user.getInternal_uuid())); + + response = new ResponseModel("success", "Profile data", data); + break; + } + + case "edit_admin_permissions": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + + UUID chatId = UUID.fromString(requestJson.getString("chat_id")); + String chatType = requestJson.getString("chat_type"); + UUID adminId = UUID.fromString(requestJson.getString("admin_id")); + JSONObject newPermissions = requestJson.getJSONObject("permissions"); + + boolean success = false; + + if (chatType.equals("group")) { + success = GroupDatabase.updateAdminPermissions(chatId, adminId, newPermissions); + } else if (chatType.equals("channel")) { + success = ChannelDatabase.updateAdminPermissions(chatId, adminId, newPermissions); + } else { + response = new ResponseModel("error", "Invalid chat type."); + break; + } + + if (success) { + JSONObject data = new JSONObject(); + data.put("chat_id", chatId.toString()); + data.put("chat_type", chatType); + data.put("permissions", newPermissions); + + JSONObject event = new JSONObject(); + event.put("action", "admin_permissions_updated"); + event.put("data", data); + + RealTimeEventDispatcher.sendToUser(adminId, event); + } + + + response = success + ? new ResponseModel("success", "Permissions updated successfully.") + : new ResponseModel("error", "Failed to update permissions."); + break; + } + + + +// case "search_chat_members": { +// if (currentUser == null) { +// response = new ResponseModel("error", "Unauthorized. Please login first."); +// break; +// } +// +// UUID chatId = UUID.fromString(requestJson.getString("chat_id")); +// String chatType = requestJson.getString("chat_type"); +// String query = requestJson.getString("query").toLowerCase(); +// +// List matched = new ArrayList<>(); +// +// if (chatType.equals("group")) { +// if (!GroupDatabase.isMember(chatId, currentUser.getInternal_uuid())) { +// response = new ResponseModel("error", "You are not a member of this group."); +// break; +// } +// matched = GroupDatabase.searchGroupMembers(chatId, query); +// } else if (chatType.equals("channel")) { +// if (!ChannelDatabase.isAdmin(chatId, currentUser.getInternal_uuid())||!ChannelDatabase.isOwner(chatId, currentUser.getInternal_uuid())) { +// response = new ResponseModel("error", "Only admins or owner can search subscribers."); +// break; +// } +// matched = ChannelDatabase.searchSubscribers(chatId, query); +// } else { +// response = new ResponseModel("error", "Invalid chat type."); +// break; +// } +// +// JSONArray arr = new JSONArray(); +// for (User u : matched) arr.put(u.toJSON()); +// response = new ResponseModel("success", "Results found", Map.of("results", arr)); +// break; +// } +// + default: response = new ResponseModel("error", "Unknown action: " + action); } + if (response == null) { + response = new ResponseModel("error", "No response generated for action: " + action); + } + JSONObject responseJson = new JSONObject(); responseJson.put("status", response.getStatus()); responseJson.put("message", response.getMessage()); responseJson.put("data", response.getData() != null ? response.getData() : JSONObject.NULL); + + if (requestJson.has("request_id")) { + String requestId = requestJson.getString("request_id"); + response.setRequestId(requestId); + responseJson.put("request_id", requestId); + } + out.println(responseJson.toString()); + System.out.println("📤 Sent response: " + responseJson.toString(2)); + } } catch (IOException e) { System.out.println("Connection with client lost."); @@ -1212,12 +1744,17 @@ public class ClientHandler implements Runnable { } finally { try { if (currentUser != null) { + //RealTime userId = currentUser.getInternal_uuid(); + List contacts = ContactDatabase.getContactUUIDs(userId); + RealTimeEventDispatcher.notifyUserStatusChanged(userId, "offline", contacts); + System.out.println("🔚 Client disconnected. Cleaning up user " + userId); userDatabase.updateUserStatus(userId, "offline"); userDatabase.updateLastSeen(userId); SessionManager.removeUser(userId); - } else { + } + else { System.out.println("❗ currentUser is null, couldn't set offline."); } socket.close(); diff --git a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java index 68e89ba..3c96120 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -176,7 +176,7 @@ public class RealTimeEventDispatcher { public static void notifyAddedToChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) { JSONObject data = new JSONObject(); - data.put("chat_type", type); // group یا channel + data.put("chat_type", type); data.put("chat_id", chatId.toString()); data.put("chat_name", chatName); data.put("image_url", imageUrl); @@ -246,5 +246,63 @@ public class RealTimeEventDispatcher { broadcastToUsers(contacts, event); } + public static void sendGroupOrChannelUpdate(String type, UUID chatId, String name, String imageUrl, String description, List affectedUsers) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); + data.put("chat_id", chatId.toString()); + data.put("name", name); + data.put("image_url", imageUrl); + data.put("description", description != null ? description : ""); + + JSONObject event = new JSONObject(); + event.put("action", "chat_updated"); + event.put("data", data); + + broadcastToUsers(affectedUsers, event); + } + + + public static void notifyBecameAdmin(String type, UUID chatId, String chatName, String imageUrl, UUID userId) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); // group or channel + data.put("chat_id", chatId.toString()); + data.put("chat_name", chatName); + data.put("image_url", imageUrl); + + JSONObject event = new JSONObject(); + event.put("action", "became_admin"); + event.put("data", data); + + sendToUser(userId, event); + } + + + public static void notifyRemovedAdminFromChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); + data.put("chat_id", chatId.toString()); + data.put("chat_name", chatName); + data.put("image_url", imageUrl); + + JSONObject event = new JSONObject(); + event.put("action", "removed_admin"); + event.put("data", data); + + sendToUser(userId, event); + } + + + public static void sendOwnershipTransferred(String type, UUID chatId, String chatName, List affectedUsers) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); // "group" or "channel" + data.put("chat_id", chatId.toString()); + data.put("chat_name", chatName); + + JSONObject event = new JSONObject(); + event.put("action", "ownership_transferred"); + event.put("data", data); + + broadcastToUsers(affectedUsers, event); + } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java b/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java new file mode 100644 index 0000000..2b6b929 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java @@ -0,0 +1,107 @@ +package org.to.telegramfinalproject.UI; + + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ClientConnection; +import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Models.User; +import org.to.telegramfinalproject.Security.PasswordHashing; + +import java.io.IOException; + +public class LoginForm { + + @FXML + private Button loginButton; + @FXML + private Button backButton; + @FXML private TextField usernameField; + @FXML private PasswordField passwordField; + + private ClientConnection connection; + + @FXML + public void initialize() { + + try { + connection = new ClientConnection("localhost", 8000); + } catch (Exception e) { + System.out.println("Could not connect to server: " + e.getMessage()); + } + + loginButton.setOnAction(e -> { + String username = usernameField.getText(); + String password = passwordField.getText(); + + JSONObject request = new JSONObject(); + request.put("action", "login"); + request.put("user_id", JSONObject.NULL); + request.put("username", username); + request.put("password", password); + request.put("profile_name", JSONObject.NULL); + if (connection!=null) { + connection.send(request.toString()); + } + userDatabase userDb = new userDatabase(); + User user = userDb.findByUsername(username); + + if(!userDb.existsByUsername(username)){ + Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid username"); + alert.show(); + } + else if(!PasswordHashing.verify(password,user.getPassword())){ + Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password"); + alert.show(); + } + else if(!PasswordHashing.verify(password,user.getPassword()) && !userDb.existsByUsername(username)){ + Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password and username"); + alert.show(); + } + else{ + try { + String responseStr = connection.receive(); + JSONObject response = new JSONObject(responseStr); + System.out.println("Status: " + response.getString("status")); + System.out.println("Message: " + response.getString("message")); + Alert alert = new Alert(Alert.AlertType.INFORMATION, " Message: " + response.getString("message")); + alert.show(); + } catch (Exception ex) { + Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage()); + alert.show(); + } + } + }); + + backButton.setOnAction(e -> { + switchScene("login_view.fxml"); + }); + + + } + + private void switchScene(String fxmlFile) { + try { + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile)); + Parent root = loader.load(); + + + Stage stage = (Stage) backButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.show(); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} + diff --git a/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java new file mode 100644 index 0000000..9bda6be --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java @@ -0,0 +1,110 @@ +package org.to.telegramfinalproject.UI; + + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ClientConnection; +import org.to.telegramfinalproject.Database.userDatabase; + +import java.io.IOException; + +public class RegisterForm { + @FXML + private Button submitButton; + @FXML + private Button backButton; + @FXML private TextField userIdField; + @FXML private TextField usernameField; + @FXML private TextField profileNameField; + @FXML private PasswordField passwordField; + @FXML private PasswordField confirmPasswordField; + + + + private ClientConnection connection; + + @FXML + public void initialize() { + + try { + connection = new ClientConnection("localhost", 8000); + } catch (Exception e) { + System.out.println("Could not connect to server: " + e.getMessage()); + } + + submitButton.setOnAction(e -> { + String userID = userIdField.getText(); + String username = usernameField.getText(); + String profile_name = profileNameField.getText(); + String password = passwordField.getText(); + String confirmPass =confirmPasswordField.getText(); + JSONObject request = new JSONObject(); + String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b"; + + userDatabase userDb = new userDatabase(); + if(password.equals(confirmPass) && password.matches(passwordRegex) && !userDb.existsByUserId(userID)&& !userDb.existsByUsername(username)){ + try { + request.put("action", "register"); + request.put("user_id", userID); + request.put("username", username); + request.put("password", password); + request.put("profile_name", profile_name); + connection.send(request.toString()); + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration is successful"); + alert.show(); + + } catch (Exception ex) { + Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage()); + alert.show(); + } + + } + else if(!password.equals(confirmPass) && password.matches(passwordRegex)) { + Alert alert = new Alert(Alert.AlertType.ERROR, "Password doesn't match"); + alert.show(); + } + else if(userDb.existsByUserId(userID)) + { + Alert alert = new Alert(Alert.AlertType.ERROR, "User ID is already exist"); + alert.show(); + } + else if(userDb.existsByUsername(username)){ + Alert alert = new Alert(Alert.AlertType.ERROR, "Username is already exist"); + alert.show(); + } + else { + Alert alert = new Alert(Alert.AlertType.ERROR, "Password isn't Strong enough"); + alert.show(); + } + }); + + backButton.setOnAction(e -> { + switchScene("login_view.fxml"); + }); + } + + private void switchScene(String fxmlFile) { + try { + + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile)); + Parent root = loader.load(); + + + Stage stage = (Stage) backButton.getScene().getWindow(); + stage.setScene(new Scene(root)); + stage.show(); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} +