RealTime (update chat list)

This commit is contained in:
2025-07-15 21:25:45 +03:30
parent b03569c1b7
commit 005302da03
8 changed files with 230 additions and 96 deletions
@@ -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<ChatEntry> 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<JSONObject> 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,17 +2061,22 @@ 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();
@@ -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()) {
@@ -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) {}
// 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());
// }
// }
//}
@@ -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<JSONObject> 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" -> {
@@ -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<ChatEntry> chatList;
public static List<ChatEntry> 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<ChatEntry> getChatList() {
return chatList;
}
}
@@ -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<JSONObject> responseQueue = new LinkedBlockingQueue<>();
public static UUID loggedInUserId = null;
public static final Map<String, BlockingQueue<JSONObject>> 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;
}
}
@@ -406,12 +406,10 @@ public class GroupDatabase {
public static List<JSONObject> getGroupAdminsAndOwner(UUID groupId) {
List<JSONObject> 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"));
@@ -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;
}
}
@@ -189,6 +189,10 @@ public class ClientHandler implements Runnable {
List<JSONObject> 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<JSONObject> 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());