From da4d1a39daec780174240002d9f0e5fc7a16faf4 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Fri, 4 Jul 2025 17:36:56 +0330 Subject: [PATCH 01/15] RealTime Action (Server) --- .../Client/IncomingMessageListener.java | 21 +- .../Database/ChannelDatabase.java | 15 + .../Database/ContactDatabase.java | 9 + .../Database/GroupDatabase.java | 15 + .../Server/ClientHandler.java | 283 ++++++++++++++++-- .../Server/RealTimeEventDispatcher.java | 46 ++- .../to/telegramfinalproject/UI/LoginForm.java | 2 +- 7 files changed, 369 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 30ca3a0..832fe3e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -1,6 +1,5 @@ package org.to.telegramfinalproject.Client; - import org.json.JSONObject; import java.io.BufferedReader; @@ -46,7 +45,9 @@ 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" -> true; default -> false; }; } @@ -111,6 +112,22 @@ public class IncomingMessageListener implements Runnable { System.out.println("Seen at: " + msg.getString("seen_at")); } + case "removed_from_group" -> { + System.out.println("\n🚫 You were removed from group: " + msg.getString("chat_id")); + } + + case "removed_from_channel" -> { + System.out.println("\n🚫 You were removed from channel: " + msg.getString("chat_id")); + } + + case "became_admin" -> { + System.out.println("\n⭐ You are now an admin in: " + msg.getString("chat_name")); + } + + case "removed_admin" -> { + System.out.println("\n⚠️ You are no longer an admin in: " + msg.getString("chat_name")); + } + default -> { System.out.println("\n❓ Unknown real-time action: " + action); } diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index efa8535..45104e4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -601,4 +601,19 @@ public class ChannelDatabase { } } + public static List getChannelSubscriberUUIDs(UUID channelId) { + List subscriberIds = new ArrayList<>(); + try (Connection conn = ConnectionDb.connect(); + 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; + } + } 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..aedde59 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -600,4 +600,19 @@ 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; + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 76022b6..6cf26ef 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."); @@ -320,6 +331,18 @@ public class ClientHandler implements Runnable { 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."); @@ -336,6 +359,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."); @@ -352,6 +382,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."); @@ -620,6 +657,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."); @@ -673,9 +724,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()); @@ -689,10 +742,6 @@ public class ClientHandler implements Runnable { case "view_channel_admins": { 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); @@ -716,6 +765,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."); @@ -764,6 +827,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."); @@ -786,10 +864,25 @@ 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; + } @@ -813,7 +906,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."); @@ -911,6 +1015,14 @@ public class ClientHandler implements Runnable { 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); @@ -944,6 +1056,13 @@ public class ClientHandler implements Runnable { boolean success = GroupDatabase.transferOwnership(groupId, newOwner.getInternal_uuid()); + //RealTime + if (success) { + List members = GroupDatabase.getMemberUUIDs(groupId); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("group", groupId, groupId.toString(), "", "", members); + } + + response = success ? new ResponseModel("success", "Ownership transferred.") : new ResponseModel("error", "Failed to transfer ownership."); @@ -972,6 +1091,14 @@ public class ClientHandler implements Runnable { case "delete_private_chat" : { 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; } @@ -995,6 +1122,9 @@ public class ClientHandler implements Runnable { boolean success = false; + + + switch (chatType) { case "group" -> success = GroupDatabase.removeMemberFromGroup(chatId, userId); case "channel" -> success = ChannelDatabase.removeSubscriberFromChannel(chatId, userId); @@ -1004,6 +1134,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.") @@ -1022,11 +1161,17 @@ 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; } @@ -1085,6 +1230,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."); @@ -1112,6 +1271,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."); @@ -1140,6 +1311,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."); @@ -1159,14 +1337,70 @@ 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": { + 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": { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); String newOwnerUserIdStr = requestJson.getString("new_owner_user_id"); @@ -1183,7 +1417,15 @@ public class ClientHandler implements Runnable { } boolean success = ChannelDatabase.transferOwnership(channelId, newOwner.getInternal_uuid()); + + //RealTime + if (success) { + List subscribers = ChannelDatabase.getChannelSubscriberUUIDs(channelId); + RealTimeEventDispatcher.sendGroupOrChannelUpdate("channel", channelId, channelId.toString(), "", "", subscribers); + } + response = success + ? new ResponseModel("success", "Ownership transferred successfully.") : new ResponseModel("error", "Failed to transfer ownership."); break; @@ -1212,12 +1454,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..be934b2 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,49 @@ 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 یا 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); + } } diff --git a/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java b/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java index 79ae18e..2b6b929 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java +++ b/src/main/java/org/to/telegramfinalproject/UI/LoginForm.java @@ -33,7 +33,7 @@ public class LoginForm { public void initialize() { try { - connection = new ClientConnection("localhost", 12345); + connection = new ClientConnection("localhost", 8000); } catch (Exception e) { System.out.println("Could not connect to server: " + e.getMessage()); } From a40500854ff4247b7f279e877e5fa03c09c5421e Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Fri, 4 Jul 2025 20:00:45 +0330 Subject: [PATCH 02/15] Remove admin in group chat --- .../Client/ActionHandler.java | 74 +++++++++++++++---- .../Database/GroupDatabase.java | 15 ++-- .../telegramfinalproject/UI/RegisterForm.java | 2 +- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 1ec86bd..a17a285 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1179,20 +1179,6 @@ 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")); - } - private void removeSubscriberFromChannel(UUID channelId) { @@ -1410,6 +1396,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("internal_uuid"); + + 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"); diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index aedde59..2869131 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -377,7 +377,7 @@ public class GroupDatabase { } catch (SQLException e) { e.printStackTrace(); } - return "member"; // پیش‌فرض + return "member"; } @@ -406,10 +406,12 @@ public class GroupDatabase { public static List getGroupAdminsAndOwner(UUID groupId) { List admins = new ArrayList<>(); - String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " + - "FROM group_members gm " + - "JOIN users u ON gm.user_id = u.internal_uuid " + - "WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')"; + String sql = """ + SELECT u.internal_uuid, u.user_id, gm.role, gm.permissions, u.profile_name + FROM group_members gm + JOIN users u ON gm.user_id = u.internal_uuid + WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin') + """; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -419,7 +421,8 @@ public class GroupDatabase { while (rs.next()) { JSONObject obj = new JSONObject(); - obj.put("user_id", rs.getObject("user_id").toString()); + obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); // این قسمت اضافه شد + obj.put("user_id", rs.getString("user_id")); obj.put("role", rs.getString("role")); obj.put("permissions", new JSONObject(rs.getString("permissions"))); obj.put("profile_name", rs.getString("profile_name")); diff --git a/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java index 4903cc8..16dc03d 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java +++ b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java @@ -35,7 +35,7 @@ public class RegisterForm { public void initialize() { try { - connection = new ClientConnection("localhost", 12345); + connection = new ClientConnection("localhost", 8000); } catch (Exception e) { System.out.println("Could not connect to server: " + e.getMessage()); } From 6f7b08203725b336e08116ccd7caee1f08189c34 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Sat, 5 Jul 2025 21:11:22 +0330 Subject: [PATCH 03/15] RealTime notify updated --- .../Client/ActionHandler.java | 180 +++++++++++++----- .../Client/EventProcessorThread.java | 18 +- .../Client/IncomingMessageListener.java | 88 ++++----- .../Client/TelegramClient.java | 38 +++- .../Server/ClientHandler.java | 159 ++++++++++++++-- 5 files changed, 360 insertions(+), 123 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index a17a285..67c367a 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -6,16 +6,19 @@ import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.SearchRequestModel; import java.io.BufferedReader; +import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; -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 ActionHandler(PrintWriter out, BufferedReader in, Scanner scanner) { this.out = out; @@ -353,6 +356,7 @@ public class ActionHandler { chatList.add(entry); + } @@ -625,11 +629,36 @@ 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; 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; + } + switch (chat.getType().trim().toLowerCase()) { case "private" -> stayInChat = showPrivateChatMenu(chat); case "group" -> stayInChat = showGroupChatMenu(chat); @@ -1752,6 +1781,7 @@ public class ActionHandler { private JSONObject getResponse() { try { return TelegramClient.responseQueue.take(); + } catch (InterruptedException e) { throw new RuntimeException("Failed to get server response"); } @@ -1774,54 +1804,54 @@ 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 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) { @@ -1987,5 +2017,55 @@ public class ActionHandler { } } + public static void requestChatList() throws IOException { + System.out.println("🟢 [requestChatList] Sending chat list request..."); + + JSONObject req = new JSONObject(); + req.put("action", "get_chat_list"); + req.put("user_id", TelegramClient.loggedInUserId.toString()); + + System.out.println("📤 [SEND] " + req.toString(2)); + + TelegramClient.send(req); + } + + 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 handleChatListResponse(JSONObject response) { +// if (response.getString("status").equals("success")) { +// JSONArray chats = response.getJSONArray("data"); +// +// TelegramClient.chatList.clear(); +// +// for (int i = 0; i < chats.length(); i++) { +// JSONObject chatJson = chats.getJSONObject(i); +// +// UUID internalId = UUID.fromString(chatJson.getString("internal_id")); +// String displayId = chatJson.getString("id"); +// String name = chatJson.getString("name"); +// String imageUrl = chatJson.optString("image_url", ""); +// String type = chatJson.getString("type"); +// LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); +// +// ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); +// TelegramClient.chatList.add(chat); +// } +// +// System.out.println("\n✅ Updated Chat List:"); +// displayChatList(); +// } else { +// System.out.println("⚠️ Failed to fetch chat list: " + 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..46c1137 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java +++ b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java @@ -8,13 +8,13 @@ public class EventProcessorThread extends Thread { setDaemon(true); } - @Override - public void run() { - while (true) { - try { - Thread.sleep(2000); - handler.processIncomingEvents(); - } catch (InterruptedException ignored) {} - } - } +// @Override +// public void run() { +// while (true) { +// try { +// Thread.sleep(2000); +// handler.processIncomingEvents(); +// } catch (InterruptedException ignored) {} +// } +// } } diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 832fe3e..9637231 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Client; import org.json.JSONObject; import java.io.BufferedReader; +import java.io.IOException; public class IncomingMessageListener implements Runnable { private final BufferedReader in; @@ -22,7 +23,11 @@ public class IncomingMessageListener implements Runnable { System.out.println("📥 Received raw line: " + line); if (response.has("action")) { + System.out.println("🎯 [Listener] Action received: " + response.toString(2)); + String action = response.getString("action"); + System.out.println("🎯 Received action: " + action); + if (isRealTimeEvent(action)) { handleRealTimeEvent(response); } else { @@ -36,7 +41,8 @@ public class IncomingMessageListener implements Runnable { } } catch (Exception e) { - System.out.println("🔴 Listener stopped: " + e.getMessage()); + System.out.println("🔴 [Listener] Crashed due to: " + e.getMessage()); + e.printStackTrace(); } } @@ -52,10 +58,44 @@ public class IncomingMessageListener implements Runnable { }; } - private void handleRealTimeEvent(JSONObject response) { + private 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("\n🔄 Chat list changed. Updating..."); + ActionHandler.requestChatList(); + + 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 "update_group_or_channel" -> { + System.out.println("\n🔄 Group/Channel info updated."); + String chatId = msg.getString("chat_id"); + String chatType = msg.getString("chat_type"); + ActionHandler.requestChatInfo(chatId, chatType); + } + + case "became_admin", "removed_admin" -> { + System.out.println("\n🔄 Your admin status changed. Updating chat info..."); + String chatId = msg.getString("chat_id"); + String chatType = msg.getString("chat_type"); + ActionHandler.requestChatInfo(chatId, chatType); + } + + default -> displayRealTimeMessage(action, msg); + } + + System.out.print(">> "); + } + + + private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { System.out.println("\n🔔 New Message:"); @@ -63,76 +103,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")); } - - case "removed_from_group" -> { - System.out.println("\n🚫 You were removed from group: " + msg.getString("chat_id")); - } - - case "removed_from_channel" -> { - System.out.println("\n🚫 You were removed from channel: " + msg.getString("chat_id")); - } - - case "became_admin" -> { - System.out.println("\n⭐ You are now an admin in: " + msg.getString("chat_name")); - } - - case "removed_admin" -> { - System.out.println("\n⚠️ You are no longer an admin in: " + msg.getString("chat_name")); - } - default -> { System.out.println("\n❓ Unknown real-time action: " + action); + System.out.println(msg.toString(2)); } } - - System.out.print(">> "); } -} +} \ 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..c8cc081 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -15,24 +15,32 @@ 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; + + 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); @@ -60,7 +68,10 @@ public class TelegramClient { handler.loginHandler(); if (Session.currentUser != null) { System.out.println("✅ Login successful."); + UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); + loggedInUserId = internalId; + handler.userMenu(internalId); } else { System.out.println("❌ Login failed."); @@ -75,6 +86,21 @@ 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(); } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 6cf26ef..f6d1986 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -327,6 +327,11 @@ 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")); @@ -350,6 +355,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) { @@ -373,6 +382,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) { @@ -489,22 +502,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", @@ -516,8 +533,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(), @@ -533,8 +550,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(), @@ -548,7 +565,6 @@ public class ClientHandler implements Runnable { )); } - chatList.sort((a, b) -> { if (a.getLastMessageTime() == null) return 1; if (b.getLastMessageTime() == null) return -1; @@ -557,14 +573,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"); @@ -600,6 +621,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"); @@ -635,6 +660,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")); @@ -681,6 +710,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"); @@ -700,6 +733,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(); // شناسه نمایشی جدید @@ -740,6 +777,10 @@ 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")); @@ -754,6 +795,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"); @@ -787,6 +832,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"); @@ -808,6 +857,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")); @@ -850,6 +903,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")); @@ -887,6 +944,10 @@ public class ClientHandler implements Runnable { 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")); @@ -927,6 +988,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()); @@ -944,6 +1009,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"); @@ -1009,6 +1078,10 @@ 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")); @@ -1035,6 +1108,10 @@ 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")); @@ -1072,6 +1149,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); @@ -1089,6 +1170,10 @@ 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"); @@ -1105,6 +1190,10 @@ public class ClientHandler implements Runnable { 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(); @@ -1116,6 +1205,10 @@ 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")); @@ -1154,6 +1247,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())) { @@ -1177,6 +1274,10 @@ public class ClientHandler implements Runnable { 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(); @@ -1192,6 +1293,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()); @@ -1216,6 +1321,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")); @@ -1251,6 +1360,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")); @@ -1291,6 +1404,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(); @@ -1330,6 +1447,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())) { @@ -1353,6 +1474,10 @@ public class ClientHandler implements Runnable { } 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")); @@ -1402,6 +1527,10 @@ public class ClientHandler implements Runnable { 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"); @@ -1442,6 +1571,8 @@ public class ClientHandler implements Runnable { responseJson.put("message", response.getMessage()); responseJson.put("data", response.getData() != null ? response.getData() : JSONObject.NULL); out.println(responseJson.toString()); + + } } catch (IOException e) { System.out.println("Connection with client lost."); From 1a79db9ba38a83cef2acf323b88c7789ba34681b Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Sat, 5 Jul 2025 22:04:08 +0330 Subject: [PATCH 04/15] Real time updated --- .../Client/ActionHandler.java | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 67c367a..0944a9a 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -2038,32 +2038,45 @@ public class ActionHandler { } -// public static void handleChatListResponse(JSONObject response) { -// if (response.getString("status").equals("success")) { -// JSONArray chats = response.getJSONArray("data"); -// -// TelegramClient.chatList.clear(); -// -// for (int i = 0; i < chats.length(); i++) { -// JSONObject chatJson = chats.getJSONObject(i); -// -// UUID internalId = UUID.fromString(chatJson.getString("internal_id")); -// String displayId = chatJson.getString("id"); -// String name = chatJson.getString("name"); -// String imageUrl = chatJson.optString("image_url", ""); -// String type = chatJson.getString("type"); -// LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); -// -// ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); -// TelegramClient.chatList.add(chat); -// } -// -// System.out.println("\n✅ Updated Chat List:"); -// displayChatList(); -// } else { -// System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); -// } -// } + public static void handleChatListResponse(JSONObject response) { + if (response.getString("status").equals("success")) { + JSONArray chats = response.getJSONArray("data"); + + Session.chatList.clear(); + + for (int i = 0; i < chats.length(); i++) { + JSONObject chatJson = chats.getJSONObject(i); + + UUID internalId = UUID.fromString(chatJson.getString("internal_id")); + String displayId = chatJson.getString("id"); + String name = chatJson.getString("name"); + String imageUrl = chatJson.optString("image_url", ""); + String type = chatJson.getString("type"); + LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); + + ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); + Session.chatList.add(chat); + } + + System.out.println("\n✅ Updated Chat List:"); + displayChatList(); + } else { + System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); + } + } + + 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()); + } + } From b03569c1b7c70fbc0e86a334a85be9b79022049b Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 8 Jul 2025 10:53:54 +0330 Subject: [PATCH 05/15] group and channel name can't be null --- .../Client/ActionHandler.java | 24 ++++++++++--- .../Database/ChannelDatabase.java | 36 ++++++++++++++----- .../Database/ConnectionDb.java | 2 +- .../Database/GroupDatabase.java | 2 +- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 0944a9a..70167f0 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -261,8 +261,16 @@ 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 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(); @@ -281,8 +289,16 @@ 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 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(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 45104e4..e589752 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,21 +586,41 @@ public class ChannelDatabase { WHERE channel_id = ? """; - try (Connection conn = ConnectionDb.connect(); - PreparedStatement stmt = conn.prepareStatement(sql)) { + String clearPermissions = """ + UPDATE channel_subscribers + SET permissions = '{}'::jsonb + WHERE channel_id = ? AND user_id = ? + """; - stmt.setObject(1, newOwnerUUID); - stmt.setObject(2, channelId); + try (Connection conn = ConnectionDb.connect()) { + conn.setAutoCommit(false); - stmt.executeUpdate(); - return true; + 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; } + + return false; } + public static List getChannelSubscriberUUIDs(UUID channelId) { List subscriberIds = new ArrayList<>(); try (Connection conn = ConnectionDb.connect(); 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/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 2869131..4375894 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -559,7 +559,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); From 005302da03d7ca5a3100f34740e9b3477075c2ab Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 15 Jul 2025 21:25:45 +0330 Subject: [PATCH 06/15] RealTime (update chat list) --- .../Client/ActionHandler.java | 157 +++++++++++------- .../Client/EventProcessorThread.java | 42 +++-- .../Client/IncomingMessageListener.java | 32 +++- .../telegramfinalproject/Client/Session.java | 29 +++- .../Client/TelegramClient.java | 11 +- .../Database/GroupDatabase.java | 19 +-- .../Models/ResponseModel.java | 18 ++ .../Server/ClientHandler.java | 18 +- 8 files changed, 230 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 70167f0..ecc6ff1 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -10,6 +10,10 @@ 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; public class ActionHandler { @@ -581,8 +585,17 @@ public class ActionHandler { } - public void userMenu(UUID internal_uuid) { + public void userMenu(UUID internal_uuid) throws IOException { while (true) { + + if (Session.forceRefreshChatList) { + System.out.println("🔁 Refresh triggered by real-time event."); + requestChatList(); // با sendWithResponse جواب می‌گیری + Session.forceRefreshChatList = false; + } + + + System.out.println("\nUser Menu:"); System.out.println("1. Show chat list"); System.out.println("2. Search"); @@ -607,35 +620,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)); } @@ -692,6 +699,12 @@ public class ActionHandler { private boolean showPrivateChatMenu(ChatEntry chat) { + + if (forceExitChat) { + forceExitChat = false; + System.out.println("🚪 Exiting chat due to real-time update."); + return false; + } System.out.println("1. Send message"); System.out.println("2. Block/Unblock"); System.out.println("3. Delete chat (one-sided)"); @@ -734,6 +747,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()); @@ -818,6 +837,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(); @@ -997,7 +1022,6 @@ public class ActionHandler { leaveChat(groupId, "group"); } - private void removeMemberFromGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1319,7 +1343,7 @@ public class ActionHandler { } JSONObject selected = admins.getJSONObject(choice); - String newOwnerId = selected.getString("user_id"); + String newOwnerId = selected.getString("internal_id"); // ✅ اصلاح شده JSONObject promoteReq = new JSONObject(); promoteReq.put("action", "transfer_channel_ownership"); @@ -2002,21 +2026,25 @@ public class ActionHandler { } - - - - - private JSONObject sendWithResponse(JSONObject request) { + private 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."); @@ -2033,18 +2061,23 @@ public class ActionHandler { } } - public static void requestChatList() throws IOException { - System.out.println("🟢 [requestChatList] Sending chat list request..."); + public void requestChatList() { JSONObject req = new JSONObject(); req.put("action", "get_chat_list"); - req.put("user_id", TelegramClient.loggedInUserId.toString()); + req.put("user_id", Session.getUserUUID()); - System.out.println("📤 [SEND] " + req.toString(2)); - - TelegramClient.send(req); + 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"); @@ -2054,32 +2087,32 @@ public class ActionHandler { } - public static void handleChatListResponse(JSONObject response) { - if (response.getString("status").equals("success")) { - JSONArray chats = response.getJSONArray("data"); - - Session.chatList.clear(); - - for (int i = 0; i < chats.length(); i++) { - JSONObject chatJson = chats.getJSONObject(i); - - UUID internalId = UUID.fromString(chatJson.getString("internal_id")); - String displayId = chatJson.getString("id"); - String name = chatJson.getString("name"); - String imageUrl = chatJson.optString("image_url", ""); - String type = chatJson.getString("type"); - LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); - - ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); - Session.chatList.add(chat); - } - - System.out.println("\n✅ Updated Chat List:"); - displayChatList(); - } else { - System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); - } - } +// public static void handleChatListResponse(JSONObject response) { +// if (response.getString("status").equals("success")) { +// JSONArray chats = response.getJSONArray("data"); +// +// Session.chatList.clear(); +// +// for (int i = 0; i < chats.length(); i++) { +// JSONObject chatJson = chats.getJSONObject(i); +// +// UUID internalId = UUID.fromString(chatJson.getString("internal_id")); +// String displayId = chatJson.getString("id"); +// String name = chatJson.getString("name"); +// String imageUrl = chatJson.optString("image_url", ""); +// String type = chatJson.getString("type"); +// LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); +// +// ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); +// Session.chatList.add(chat); +// } +// +// System.out.println("\n✅ Updated Chat List:"); +// displayChatList(); +// } else { +// System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); +// } +// } public static void displayChatList() { if (Session.chatList == null || Session.chatList.isEmpty()) { diff --git a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java index 46c1137..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; +//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() { -// while (true) { -// try { -// Thread.sleep(2000); -// handler.processIncomingEvents(); -// } catch (InterruptedException ignored) {} +// 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 9637231..af5413f 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -4,6 +4,7 @@ import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; +import java.util.concurrent.BlockingQueue; public class IncomingMessageListener implements Runnable { private final BufferedReader in; @@ -22,10 +23,20 @@ public class IncomingMessageListener implements Runnable { JSONObject response = new JSONObject(line); System.out.println("📥 Received raw line: " + line); - if (response.has("action")) { - System.out.println("🎯 [Listener] Action received: " + response.toString(2)); + //if it has reqID answer + if (response.has("request_id")) { + String requestId = response.getString("request_id"); + BlockingQueue queue = TelegramClient.pendingResponses.get(requestId); + if (queue != null) { + queue.put(response); // send it to the correct line + 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)) { @@ -33,10 +44,11 @@ public class IncomingMessageListener implements Runnable { } 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 } } @@ -65,8 +77,13 @@ public class IncomingMessageListener implements Runnable { switch (action) { case "added_to_group", "added_to_channel", "removed_from_group", "removed_from_channel", "chat_deleted" -> { - System.out.println("\n🔄 Chat list changed. Updating..."); - ActionHandler.requestChatList(); + 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..."); @@ -94,7 +111,6 @@ public class IncomingMessageListener implements Runnable { System.out.print(">> "); } - private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { @@ -135,4 +151,4 @@ public class IncomingMessageListener implements Runnable { } } } -} \ No newline at end of file +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 64270a1..bcd741c 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -1,15 +1,20 @@ 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 String getUserUUID() { if (currentUser.has("uuid")) return currentUser.getString("uuid"); @@ -18,4 +23,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 c8cc081..c294739 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -7,9 +7,11 @@ 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 { @@ -22,6 +24,8 @@ public class TelegramClient { 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; @@ -53,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"); @@ -104,4 +108,9 @@ public class TelegramClient { public static void main(String[] args) { new TelegramClient().start(); } + + public PrintWriter getOut() { + return out; + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 4375894..a1f5b03 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -406,12 +406,10 @@ public class GroupDatabase { public static List getGroupAdminsAndOwner(UUID groupId) { List admins = new ArrayList<>(); - String sql = """ - SELECT u.internal_uuid, u.user_id, gm.role, gm.permissions, u.profile_name - FROM group_members gm - JOIN users u ON gm.user_id = u.internal_uuid - WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin') - """; + String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " + + "FROM group_members gm " + + "JOIN users u ON gm.user_id = u.internal_uuid " + + "WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -421,8 +419,7 @@ public class GroupDatabase { while (rs.next()) { JSONObject obj = new JSONObject(); - obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); // این قسمت اضافه شد - obj.put("user_id", rs.getString("user_id")); + obj.put("user_id", rs.getObject("user_id").toString()); obj.put("role", rs.getString("role")); obj.put("permissions", new JSONObject(rs.getString("permissions"))); obj.put("profile_name", rs.getString("profile_name")); @@ -447,7 +444,7 @@ public class GroupDatabase { stmt.setObject(2, userId); ResultSet rs = stmt.executeQuery(); - return rs.next(); // اگر رکوردی پیدا شد یعنی owner است + return rs.next(); } catch (SQLException e) { e.printStackTrace(); } @@ -463,7 +460,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(); @@ -511,7 +508,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")); 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 f6d1986..cdc1cfd 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -189,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)) { @@ -241,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)) { @@ -410,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"); @@ -1114,7 +1122,6 @@ public class ClientHandler implements Runnable { } 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."); @@ -1570,6 +1577,15 @@ public class ClientHandler implements Runnable { 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")) { + response.setRequestId(requestJson.getString("request_id")); + } + + if (requestJson.has("request_id")) { + String requestId = requestJson.getString("request_id"); + response.setRequestId(requestId); + responseJson.put("request_id", requestId); + } out.println(responseJson.toString()); From b387137a00e772be7909fd03475dd20d59ecf6ac Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 15 Jul 2025 23:30:14 +0330 Subject: [PATCH 07/15] RealTime (update chat list automatically) --- .../Client/ActionHandler.java | 58 +++++++++++++++++-- .../telegramfinalproject/Client/Session.java | 4 ++ .../Client/TelegramClient.java | 1 + 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index ecc6ff1..b547c07 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -588,12 +588,18 @@ public class ActionHandler { public void userMenu(UUID internal_uuid) throws IOException { while (true) { - if (Session.forceRefreshChatList) { - System.out.println("🔁 Refresh triggered by real-time event."); - requestChatList(); // با sendWithResponse جواب می‌گیری - Session.forceRefreshChatList = false; + + if (Session.backToChatList) { + Session.backToChatList = false; + return; } +// if (Session.forceRefreshChatList) { +// System.out.println("🔁 Refresh triggered by real-time event."); +// requestChatList(); // با sendWithResponse جواب می‌گیری +// Session.forceRefreshChatList = false; +// } + System.out.println("\nUser Menu:"); @@ -606,7 +612,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(); @@ -2062,7 +2072,7 @@ public class ActionHandler { } - public void requestChatList() { + public static void requestChatList() { JSONObject req = new JSONObject(); req.put("action", "get_chat_list"); req.put("user_id", Session.getUserUUID()); @@ -2127,7 +2137,43 @@ public class ActionHandler { } } + 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(); // این متدی باشه که چت‌ها رو نمایش بده + System.out.print("Select a chat by number: "); + } + } + + Thread.sleep(300); // جلوگیری از مصرف بی‌مورد CPU + } catch (Exception e) { + System.out.println("❌ ChatStateMonitor crashed: " + e.getMessage()); + } + } + } + } } + + diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index bcd741c..590a4a5 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -14,6 +14,10 @@ public class Session { public static JSONObject currentUser; 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 getUserUUID() { diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index c294739..d74c5bc 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -72,6 +72,7 @@ public class TelegramClient { handler.loginHandler(); if (Session.currentUser != null) { System.out.println("✅ Login successful."); + new Thread(new ActionHandler.ChatStateMonitor(out)).start(); UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); loggedInUserId = internalId; From 541f800877bba548b18b881f5e26365023d557c5 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Mon, 21 Jul 2025 13:44:22 +0330 Subject: [PATCH 08/15] RealTime updated(add amin/remove admin) --- .../Client/ActionHandler.java | 245 +++++++++++++++++- .../Client/IncomingMessageListener.java | 146 ++++++++++- .../telegramfinalproject/Client/Session.java | 6 + .../Client/TelegramClient.java | 5 + .../Models/ChatEntry.java | 23 +- .../Server/ClientHandler.java | 13 +- 6 files changed, 414 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index b547c07..dc1e335 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -21,13 +21,19 @@ public class ActionHandler { 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() { @@ -225,9 +231,10 @@ 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"); + JSONArray chatListJson = data.getJSONArray("chat_list"); List chatList = new ArrayList<>(); for (Object obj : chatListJson) { @@ -250,7 +257,13 @@ public class ActionHandler { Session.chatList = chatList; System.out.println("✅ Chat list updated."); } else { + System.out.println("⚠️ Response has no chat list data."); + } + } 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) { @@ -261,7 +274,6 @@ public class ActionHandler { - public void createGroup() { System.out.print("Enter group ID: "); String groupId = scanner.nextLine(); @@ -338,19 +350,35 @@ public class ActionHandler { String action = request.getString("action"); this.out.println(request.toString()); - JSONObject response = TelegramClient.responseQueue.take(); + JSONObject response = null; + + // منتظر بمون تا پاسخ واقعی (یعنی status داشته باشه) بیاد + 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": @@ -685,6 +713,14 @@ public class ActionHandler { 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..."); @@ -702,6 +738,12 @@ public class ActionHandler { } } } + + Session.inChatMenu = false; + Session.currentChatId = null; + Session.currentChatEntry = null; + Session.currentChatType = null; + } @@ -1522,7 +1564,7 @@ public class ActionHandler { } JSONObject selected = eligible.get(choice); - String targetInternalUUID = selected.getString("internal_uuid"); + String targetInternalUUID = selected.getString("user_id"); JSONObject removeReq = new JSONObject(); removeReq.put("action", "remove_admin_from_group"); @@ -2036,7 +2078,7 @@ public class ActionHandler { } - private static 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."); @@ -2174,6 +2216,187 @@ public class ActionHandler { } + 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..."); + + // جستجو در لیست چت‌ها با استفاده از currentChatId + 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"); + } + + + + public static void requestPermissions(String chatId, String chatType) { + JSONObject permissionReq = new JSONObject(); + permissionReq.put("action", "get_permissions"); + permissionReq.put("receiver_id", chatId); + permissionReq.put("receiver_type", chatType); + TelegramClient.send(permissionReq); + } + + public void refreshCurrentChat() { + switch (Session.currentChatType.toLowerCase()) { + case "group" -> showGroupChatMenu(Session.currentChatEntry); + case "channel" -> showChannelChatMenu(Session.currentChatEntry); + } + } + + + 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; // ❗ اینجا بجای return باید continue باشه + } + + + switch (Session.currentChatEntry.getType()) { + case "group" -> showGroupChatMenu(Session.currentChatEntry); + case "channel" -> showChannelChatMenu(Session.currentChatEntry); + } + } + } catch (Exception ignored) {} + } + }); + refresher.setDaemon(true); + refresher.start(); + } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index af5413f..588c9be 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -1,9 +1,12 @@ 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 { @@ -26,13 +29,23 @@ public class IncomingMessageListener implements Runnable { //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); // send it to the correct line - continue; + queue.put(response); + } else { + System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue..."); + TelegramClient.responseQueue.put(response); } + + // ⚠️ بسیار مهم: دیگر بررسی action انجام نده + continue; } + + //if it has action check it if (response.has("action")) { String action = response.getString("action"); @@ -70,10 +83,11 @@ public class IncomingMessageListener implements Runnable { }; } - private void handleRealTimeEvent(JSONObject response) throws IOException { + 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" -> { @@ -99,18 +113,136 @@ public class IncomingMessageListener implements Runnable { } case "became_admin", "removed_admin" -> { - System.out.println("\n🔄 Your admin status changed. Updating chat info..."); - String chatId = msg.getString("chat_id"); - String chatType = msg.getString("chat_type"); - ActionHandler.requestChatInfo(chatId, chatType); + System.out.println("🧩 Detected admin role change. Calling handler..."); + new Thread(() -> { + try { + handleAdminRoleChanged(msg); // ✅ صدا زدن در ترد جداگانه + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); } + +// case "get_chat_info" -> { +// JSONObject chatData = msg.getJSONObject("data"); +// UUID chatUUID = UUID.fromString(chatData.getString("internal_id")); +// +// Optional entry = Session.chatList.stream() +// .filter(e -> e.getId().equals(chatUUID)) +// .findFirst(); +// +// entry.ifPresent(chat -> { +// chat.setAdmin(chatData.optBoolean("is_admin", false)); +// chat.setOwner(chatData.optBoolean("is_owner", false)); +// +// if (Session.currentChatId != null && Session.currentChatId.equals(chat.getId())) { +// Session.currentChatEntry = chat; +// Session.refreshCurrentChatMenu = true; +// +// } +// +// System.out.println("✅ Chat info updated → admin: " + chat.isAdmin() + ", owner: " + chat.isOwner()); +// }); +// } + + + 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. دریافت 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. گرفتن 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. ست کردن 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" -> { diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 590a4a5..f849e41 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -16,6 +16,12 @@ public class Session { 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; + diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index d74c5bc..8db405e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -73,6 +73,8 @@ public class TelegramClient { 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; @@ -102,6 +104,9 @@ public class TelegramClient { } } + + + public static Socket getSocket() { return socket; } 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/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index cdc1cfd..7254d16 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1533,6 +1533,7 @@ public class ClientHandler implements Runnable { } + case "transfer_channel_ownership": { if (currentUser == null) { response = new ResponseModel("error", "Unauthorized. Please login first."); @@ -1573,21 +1574,23 @@ public class ClientHandler implements Runnable { 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")) { - response.setRequestId(requestJson.getString("request_id")); - } if (requestJson.has("request_id")) { String requestId = requestJson.getString("request_id"); response.setRequestId(requestId); - responseJson.put("request_id", requestId); + responseJson.put("request_id", requestId); // ✅ بدون این، کلاینت قفل میشه } - out.println(responseJson.toString()); + out.println(responseJson.toString()); + System.out.println("📤 Sent response: " + responseJson.toString(2)); } } catch (IOException e) { From 84890539da01f1ec2c1234a9a576d692b6774206 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Mon, 21 Jul 2025 18:59:24 +0330 Subject: [PATCH 09/15] fixed real time bugs --- .../Client/ActionHandler.java | 47 +++++-------------- .../Client/IncomingMessageListener.java | 34 +++----------- .../Server/ClientHandler.java | 14 +++++- .../Server/RealTimeEventDispatcher.java | 16 ++++++- 4 files changed, 45 insertions(+), 66 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index dc1e335..5c4bba8 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -231,9 +231,17 @@ public class ActionHandler { try { JSONObject response = TelegramClient.responseQueue.take(); + if (response != null && response.getString("status").equals("success")) { if (response.has("data") && !response.isNull("data")) { JSONObject data = response.getJSONObject("data"); + + // ❗ چک کن که chat_list وجود داره + 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<>(); @@ -255,9 +263,9 @@ 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 chat list data."); + System.out.println("⚠️ Response has no data object."); } } else { if (response.has("message") && !response.isNull("message")) { @@ -273,7 +281,6 @@ public class ActionHandler { } - public void createGroup() { System.out.print("Enter group ID: "); String groupId = scanner.nextLine(); @@ -622,11 +629,6 @@ public class ActionHandler { return; } -// if (Session.forceRefreshChatList) { -// System.out.println("🔁 Refresh triggered by real-time event."); -// requestChatList(); // با sendWithResponse جواب می‌گیری -// Session.forceRefreshChatList = false; -// } @@ -869,7 +871,6 @@ public class ActionHandler { case "9" -> { if (isOwner) { transferOwnershipAndLeave(chat.getId()); - refreshChatList(); } else { leaveChat(chat.getId(), "group"); refreshChatList(); @@ -1003,7 +1004,6 @@ public class ActionHandler { case "9" -> { if (isOwner) { transferChannelOwnershipAndLeave(chat.getId()); - refreshChatList(); return false; } else { System.out.println("❌ You don't have permission."); @@ -2139,32 +2139,7 @@ public class ActionHandler { } -// public static void handleChatListResponse(JSONObject response) { -// if (response.getString("status").equals("success")) { -// JSONArray chats = response.getJSONArray("data"); -// -// Session.chatList.clear(); -// -// for (int i = 0; i < chats.length(); i++) { -// JSONObject chatJson = chats.getJSONObject(i); -// -// UUID internalId = UUID.fromString(chatJson.getString("internal_id")); -// String displayId = chatJson.getString("id"); -// String name = chatJson.getString("name"); -// String imageUrl = chatJson.optString("image_url", ""); -// String type = chatJson.getString("type"); -// LocalDateTime lastMessageTime = LocalDateTime.parse(chatJson.getString("last_message_time")); -// -// ChatEntry chat = new ChatEntry(internalId, displayId, name, imageUrl, type, lastMessageTime); -// Session.chatList.add(chat); -// } -// -// System.out.println("\n✅ Updated Chat List:"); -// displayChatList(); -// } else { -// System.out.println("⚠️ Failed to fetch chat list: " + response.getString("message")); -// } -// } + public static void displayChatList() { if (Session.chatList == null || Session.chatList.isEmpty()) { diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 588c9be..c10c83d 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -40,7 +40,6 @@ public class IncomingMessageListener implements Runnable { TelegramClient.responseQueue.put(response); } - // ⚠️ بسیار مهم: دیگر بررسی action انجام نده continue; } @@ -112,11 +111,11 @@ public class IncomingMessageListener implements Runnable { ActionHandler.requestChatInfo(chatId, chatType); } - case "became_admin", "removed_admin" -> { - System.out.println("🧩 Detected admin role change. Calling handler..."); + case "became_admin", "removed_admin", "ownership_transferred" -> { + System.out.println("🧩 Detected admin/owner role change. Calling handler..."); new Thread(() -> { try { - handleAdminRoleChanged(msg); // ✅ صدا زدن در ترد جداگانه + handleAdminRoleChanged(msg); //new thread } catch (IOException e) { e.printStackTrace(); } @@ -124,27 +123,7 @@ public class IncomingMessageListener implements Runnable { } -// case "get_chat_info" -> { -// JSONObject chatData = msg.getJSONObject("data"); -// UUID chatUUID = UUID.fromString(chatData.getString("internal_id")); // -// Optional entry = Session.chatList.stream() -// .filter(e -> e.getId().equals(chatUUID)) -// .findFirst(); -// -// entry.ifPresent(chat -> { -// chat.setAdmin(chatData.optBoolean("is_admin", false)); -// chat.setOwner(chatData.optBoolean("is_owner", false)); -// -// if (Session.currentChatId != null && Session.currentChatId.equals(chat.getId())) { -// Session.currentChatEntry = chat; -// Session.refreshCurrentChatMenu = true; -// -// } -// -// System.out.println("✅ Chat info updated → admin: " + chat.isAdmin() + ", owner: " + chat.isOwner()); -// }); -// } @@ -166,7 +145,7 @@ public class IncomingMessageListener implements Runnable { System.out.println("\n🔄 Your admin status changed. Updating chat info..."); try { - // 1. دریافت chat info + // 1. get chat info JSONObject chatInfoReq = new JSONObject(); chatInfoReq.put("action", "get_chat_info"); chatInfoReq.put("receiver_id", chatId); @@ -196,7 +175,7 @@ public class IncomingMessageListener implements Runnable { Session.currentChatEntry = chat; }); - // 2. گرفتن permission + // 2. get permission JSONObject permissionReq = new JSONObject(); if (chatType.equalsIgnoreCase("group")) { permissionReq.put("action", "get_group_permissions"); @@ -210,10 +189,9 @@ public class IncomingMessageListener implements Runnable { JSONObject perm = permissionResp.getJSONObject("data"); entry.ifPresent(chat -> chat.setPermissions(perm)); - // 3. ست کردن currentChatId + // 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); diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 7254d16..0decb6e 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1143,10 +1143,16 @@ public class ClientHandler implements Runnable { //RealTime if (success) { List members = GroupDatabase.getMemberUUIDs(groupId); + + // 1. ارسال ownership_transferred فقط به owner جدید + RealTimeEventDispatcher.sendOwnershipTransferred("group", groupId, groupId.toString(), List.of(newOwner.getInternal_uuid())); + + // 2. ارسال update_group_or_channel برای همه اعضا RealTimeEventDispatcher.sendGroupOrChannelUpdate("group", groupId, groupId.toString(), "", "", members); } + response = success ? new ResponseModel("success", "Ownership transferred.") : new ResponseModel("error", "Failed to transfer ownership."); @@ -1558,9 +1564,15 @@ public class ClientHandler implements Runnable { //RealTime if (success) { List subscribers = ChannelDatabase.getChannelSubscriberUUIDs(channelId); + + // 1. ارسال ownership_transferred فقط به owner جدید + RealTimeEventDispatcher.sendOwnershipTransferred("channel", channelId, channelId.toString(), List.of(newOwner.getInternal_uuid())); + + // 2. ارسال update_group_or_channel برای بروزرسانی UI تمام افراد RealTimeEventDispatcher.sendGroupOrChannelUpdate("channel", channelId, channelId.toString(), "", "", subscribers); } + response = success ? new ResponseModel("success", "Ownership transferred successfully.") @@ -1586,7 +1598,7 @@ public class ClientHandler implements Runnable { if (requestJson.has("request_id")) { String requestId = requestJson.getString("request_id"); response.setRequestId(requestId); - responseJson.put("request_id", requestId); // ✅ بدون این، کلاینت قفل میشه + responseJson.put("request_id", requestId); } out.println(responseJson.toString()); diff --git a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java index be934b2..3c96120 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -264,7 +264,7 @@ public class RealTimeEventDispatcher { public static void notifyBecameAdmin(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); // group or channel data.put("chat_id", chatId.toString()); data.put("chat_name", chatName); data.put("image_url", imageUrl); @@ -291,4 +291,18 @@ public class RealTimeEventDispatcher { 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); + } + } From 42c92c5a2d4ef90cf540c331735867a9a6855117 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 22 Jul 2025 17:00:36 +0330 Subject: [PATCH 10/15] fixed real time for transfer the ownership and update group/channel info --- .../Client/ActionHandler.java | 4 +- .../Client/IncomingMessageListener.java | 18 ++++++--- .../Server/ClientHandler.java | 40 ++++++++++++++++--- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 5c4bba8..e4f3d29 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -873,7 +873,7 @@ public class ActionHandler { transferOwnershipAndLeave(chat.getId()); } else { leaveChat(chat.getId(), "group"); - refreshChatList(); +// refreshChatList(); } return false; } @@ -1395,7 +1395,7 @@ public class ActionHandler { } JSONObject selected = admins.getJSONObject(choice); - String newOwnerId = selected.getString("internal_id"); // ✅ اصلاح شده + String newOwnerId = selected.getString("internal_id"); JSONObject promoteReq = new JSONObject(); promoteReq.put("action", "transfer_channel_ownership"); diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index c10c83d..7b494a4 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -23,6 +23,8 @@ public class IncomingMessageListener implements Runnable { String line; while ((line = in.readLine()) != null) { + + JSONObject response = new JSONObject(line); System.out.println("📥 Received raw line: " + line); @@ -77,7 +79,7 @@ public class IncomingMessageListener implements Runnable { "update_group_or_channel", "chat_deleted", "blocked_by_user", "unblocked_by_user", "message_seen", "removed_from_group", "removed_from_channel", - "became_admin", "removed_admin" -> true; + "became_admin", "removed_admin", "ownership_transferred" -> true; default -> false; }; } @@ -104,11 +106,15 @@ public class IncomingMessageListener implements Runnable { } } - case "update_group_or_channel" -> { + case "chat_updated" -> { System.out.println("\n🔄 Group/Channel info updated."); - String chatId = msg.getString("chat_id"); - String chatType = msg.getString("chat_type"); - ActionHandler.requestChatInfo(chatId, chatType); + new Thread(() -> { + try { + handleAdminRoleChanged(msg); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); } case "became_admin", "removed_admin", "ownership_transferred" -> { @@ -190,7 +196,7 @@ public class IncomingMessageListener implements Runnable { entry.ifPresent(chat -> chat.setPermissions(perm)); // 3. set currentChatId - Session.currentChatId = chatUUID.toString(); // ❗ باید قبل از بررسی شرط‌ها باشه + Session.currentChatId = chatUUID.toString(); System.out.println("🧪 Checking refresh conditions..."); System.out.println("🔹 inChatMenu: " + Session.inChatMenu); diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 0decb6e..db3e7f1 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1143,16 +1143,30 @@ public class ClientHandler implements Runnable { //RealTime if (success) { List members = GroupDatabase.getMemberUUIDs(groupId); + Group group = GroupDatabase.findByInternalUUID(groupId); - // 1. ارسال ownership_transferred فقط به owner جدید - RealTimeEventDispatcher.sendOwnershipTransferred("group", groupId, groupId.toString(), List.of(newOwner.getInternal_uuid())); + //update for new owner + RealTimeEventDispatcher.sendOwnershipTransferred( + "group", + groupId, + group.getGroup_name(), + List.of(newOwner.getInternal_uuid()) + ); - // 2. ارسال update_group_or_channel برای همه اعضا - RealTimeEventDispatcher.sendGroupOrChannelUpdate("group", groupId, groupId.toString(), "", "", members); + //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."); @@ -1564,15 +1578,29 @@ public class ClientHandler implements Runnable { //RealTime if (success) { List subscribers = ChannelDatabase.getChannelSubscriberUUIDs(channelId); + Channel channel = ChannelDatabase.findByInternalUUID(channelId); // 🟢 گرفتن اطلاعات کامل // 1. ارسال ownership_transferred فقط به owner جدید - RealTimeEventDispatcher.sendOwnershipTransferred("channel", channelId, channelId.toString(), List.of(newOwner.getInternal_uuid())); + RealTimeEventDispatcher.sendOwnershipTransferred( + "channel", + channelId, + channel.getChannel_name(), // ✅ اسم واقعی کانال + List.of(newOwner.getInternal_uuid()) + ); // 2. ارسال update_group_or_channel برای بروزرسانی UI تمام افراد - RealTimeEventDispatcher.sendGroupOrChannelUpdate("channel", channelId, channelId.toString(), "", "", subscribers); + RealTimeEventDispatcher.sendGroupOrChannelUpdate( + "channel", + channelId, + channel.getChannel_name(), // ✅ name واقعی + channel.getImage_url(), // ✅ image + channel.getDescription(), // ✅ description + subscribers + ); } + response = success ? new ResponseModel("success", "Ownership transferred successfully.") From 4a849ea02239e1ff6b18ec33ad7afa6ffc909fcd Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 22 Jul 2025 18:08:18 +0330 Subject: [PATCH 11/15] View profile --- .../Client/ActionHandler.java | 166 ++++++++++++------ .../Database/userDatabase.java | 47 ++++- .../Server/ClientHandler.java | 21 +++ 3 files changed, 183 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index e4f3d29..4c45a0e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -730,6 +730,10 @@ public class ActionHandler { 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); @@ -759,11 +763,34 @@ public class ActionHandler { 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(); @@ -779,6 +806,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."); @@ -836,6 +889,7 @@ public class ActionHandler { } else { System.out.println("9. Leave Group"); } + System.out.println("10. View Profile"); System.out.println("0. Back to Chat List"); @@ -877,6 +931,9 @@ public class ActionHandler { } return false; } + case "10" ->{ + GroupInfo(chat.getId()); + } case "0" -> { return false; @@ -939,6 +996,7 @@ public class ActionHandler { System.out.println("8. Leave Channel"); } + System.out.println("10. View Profile"); System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); @@ -1009,6 +1067,9 @@ public class ActionHandler { System.out.println("❌ You don't have permission."); } } + case "10"->{ + ChannelInfo(chat.getId()); + } case "0" -> { return false; } @@ -1693,6 +1754,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(); @@ -1792,7 +1881,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) { @@ -1896,55 +2011,6 @@ 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: "); 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/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index db3e7f1..ac7da87 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1608,6 +1608,27 @@ public class ClientHandler implements Runnable { 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; + } + + default: From 28af57f181d9c76948000d7aa3bafafc8056d211 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 22 Jul 2025 22:19:35 +0330 Subject: [PATCH 12/15] open foreign chat before join/ add contact --- .../Client/ActionHandler.java | 229 ++++++++++++++---- 1 file changed, 180 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 4c45a0e..93d2cdb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -4,6 +4,7 @@ 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; @@ -359,7 +360,6 @@ public class ActionHandler { JSONObject response = null; - // منتظر بمون تا پاسخ واقعی (یعنی status داشته باشه) بیاد while (true) { JSONObject incoming = TelegramClient.responseQueue.take(); @@ -488,24 +488,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" -> { @@ -521,24 +530,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" -> { @@ -927,7 +945,7 @@ public class ActionHandler { transferOwnershipAndLeave(chat.getId()); } else { leaveChat(chat.getId(), "group"); -// refreshChatList(); + //refreshChatList(); } return false; } @@ -2393,20 +2411,20 @@ public class ActionHandler { - public static void requestPermissions(String chatId, String chatType) { - JSONObject permissionReq = new JSONObject(); - permissionReq.put("action", "get_permissions"); - permissionReq.put("receiver_id", chatId); - permissionReq.put("receiver_type", chatType); - TelegramClient.send(permissionReq); - } - - public void refreshCurrentChat() { - switch (Session.currentChatType.toLowerCase()) { - case "group" -> showGroupChatMenu(Session.currentChatEntry); - case "channel" -> showChannelChatMenu(Session.currentChatEntry); - } - } +// public static void requestPermissions(String chatId, String chatType) { +// JSONObject permissionReq = new JSONObject(); +// permissionReq.put("action", "get_permissions"); +// permissionReq.put("receiver_id", chatId); +// permissionReq.put("receiver_type", chatType); +// TelegramClient.send(permissionReq); +// } +// +// public void refreshCurrentChat() { +// switch (Session.currentChatType.toLowerCase()) { +// case "group" -> showGroupChatMenu(Session.currentChatEntry); +// case "channel" -> showChannelChatMenu(Session.currentChatEntry); +// } +// } private void startMenuRefresherThread() { @@ -2420,7 +2438,7 @@ public class ActionHandler { Session.refreshCurrentChatMenu = false; if (Session.currentChatEntry == null) { System.out.println("⚠️ currentChatEntry is null. Skipping..."); - continue; // ❗ اینجا بجای return باید continue باشه + continue; } @@ -2438,6 +2456,119 @@ public class ActionHandler { + 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")); + } + } + + + } From 626b3c2fb4101410811de860ff3d574167a779a0 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 22 Jul 2025 23:07:03 +0330 Subject: [PATCH 13/15] Edit transfer ownerShip --- .../Client/ActionHandler.java | 171 ++++++++++++------ 1 file changed, 116 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 93d2cdb..88e7114 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1121,38 +1121,68 @@ 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"); } + private void removeMemberFromGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1455,39 +1485,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("internal_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"); } From a26736b6ba0b78a5a35cbd1757d5df3772e69856 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Thu, 24 Jul 2025 18:15:34 +0330 Subject: [PATCH 14/15] add edit admin permissions --- .../Client/ActionHandler.java | 154 +++++++++++++----- .../Client/IncomingMessageListener.java | 4 +- .../Database/ChannelDatabase.java | 14 ++ .../Database/GroupDatabase.java | 14 ++ .../Server/ClientHandler.java | 44 +++++ 5 files changed, 185 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 88e7114..4b522ef 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -909,6 +909,10 @@ public class ActionHandler { } 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(); @@ -952,6 +956,11 @@ public class ActionHandler { case "10" ->{ GroupInfo(chat.getId()); } + case "11" -> { + if (isOwner) { + editAdminPermissions(chat.getId(), "group"); + } + } case "0" -> { return false; @@ -1015,6 +1024,10 @@ public class ActionHandler { } 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(); @@ -1088,6 +1101,13 @@ public class ActionHandler { case "10"->{ ChannelInfo(chat.getId()); } + case "11" -> { + if (isOwner) { + editAdminPermissions(chat.getId(), "channel"); + } + } + + case "0" -> { return false; } @@ -2113,33 +2133,100 @@ public class ActionHandler { } - 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())); - + private void editAdminPermissions(UUID chatId, String chatType) { 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")); } @@ -2181,26 +2268,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) { diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 7b494a4..e10e03e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -79,7 +79,7 @@ public class IncomingMessageListener implements Runnable { "update_group_or_channel", "chat_deleted", "blocked_by_user", "unblocked_by_user", "message_seen", "removed_from_group", "removed_from_channel", - "became_admin", "removed_admin", "ownership_transferred" -> true; + "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> true; default -> false; }; } @@ -117,7 +117,7 @@ public class IncomingMessageListener implements Runnable { }).start(); } - case "became_admin", "removed_admin", "ownership_transferred" -> { + case "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> { System.out.println("🧩 Detected admin/owner role change. Calling handler..."); new Thread(() -> { try { diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index e589752..6804ad4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -636,4 +636,18 @@ public class ChannelDatabase { return subscriberIds; } + 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.setObject(3, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index a1f5b03..b0a54ae 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -615,4 +615,18 @@ public class GroupDatabase { 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; + } + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index ac7da87..13d95ec 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1628,6 +1628,50 @@ public class ClientHandler implements Runnable { 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; + } + + + From f54bffea43a849a3836ae792d473d76f4869a31e Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Thu, 24 Jul 2025 19:30:05 +0330 Subject: [PATCH 15/15] Edit create group/channel form --- .../Client/ActionHandler.java | 126 ++++++++++-------- .../Database/GroupDatabase.java | 50 +++++++ .../Server/ClientHandler.java | 35 +++++ 3 files changed, 156 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 4b522ef..5d51ac6 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -57,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"); @@ -212,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 + "]", "", @@ -237,7 +264,7 @@ public class ActionHandler { if (response.has("data") && !response.isNull("data")) { JSONObject data = response.getJSONObject("data"); - // ❗ چک کن که chat_list وجود داره + //is chat list available if (!data.has("chat_list") || data.isNull("chat_list")) { System.out.println("❌ chat_list not found in response data."); return; @@ -283,8 +310,19 @@ public class ActionHandler { public void createGroup() { - System.out.print("Enter group ID: "); - String groupId = 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: "); @@ -311,8 +349,18 @@ public class ActionHandler { public void createChannel() { - System.out.print("Enter channel ID: "); - String channelId = 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: "); @@ -367,8 +415,8 @@ public class ActionHandler { response = incoming; break; } else { - // این یک پیام Real-Time است - handleRealTime(incoming); // ✅ + //real time + handleRealTime(incoming); } } @@ -1377,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): "); @@ -1394,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); @@ -2111,26 +2159,7 @@ public class ActionHandler { - 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())); - - 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 editAdminPermissions(UUID chatId, String chatType) { @@ -2390,12 +2419,12 @@ public class ActionHandler { ActionHandler.requestChatList(); if (Session.inChatListMenu) { System.out.println("\n📬 Updated Chat List:"); - ActionHandler.displayChatList(); // این متدی باشه که چت‌ها رو نمایش بده + ActionHandler.displayChatList(); //Show chats System.out.print("Select a chat by number: "); } } - Thread.sleep(300); // جلوگیری از مصرف بی‌مورد CPU + Thread.sleep(300); } catch (Exception e) { System.out.println("❌ ChatStateMonitor crashed: " + e.getMessage()); } @@ -2420,7 +2449,7 @@ public class ActionHandler { if (Session.refreshCurrentChatMenu && Session.inChatMenu && Session.currentChatId != null) { System.out.println("🔁 [Refresher] Refresh requested. Searching for chat..."); - // جستجو در لیست چت‌ها با استفاده از currentChatId + ChatEntry chat = Session.chatList.stream() .filter(e -> e.getId().toString().equals(Session.currentChatId)) .findFirst() @@ -2540,20 +2569,7 @@ public class ActionHandler { -// public static void requestPermissions(String chatId, String chatType) { -// JSONObject permissionReq = new JSONObject(); -// permissionReq.put("action", "get_permissions"); -// permissionReq.put("receiver_id", chatId); -// permissionReq.put("receiver_type", chatType); -// TelegramClient.send(permissionReq); -// } -// -// public void refreshCurrentChat() { -// switch (Session.currentChatType.toLowerCase()) { -// case "group" -> showGroupChatMenu(Session.currentChatEntry); -// case "channel" -> showChannelChatMenu(Session.currentChatEntry); -// } -// } + private void startMenuRefresherThread() { diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index b0a54ae..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; @@ -629,4 +630,53 @@ public class GroupDatabase { } } + 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/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 13d95ec..e76ce67 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1672,6 +1672,41 @@ public class ClientHandler implements Runnable { +// 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; +// } +//