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) {