From e62ffbc655861925205ca4dce3711dd252cc9e92 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 2 Sep 2025 18:12:49 +0330 Subject: [PATCH] fix --- .../Client/ActionHandler.java | 578 ++---------- .../Client/IncomingMessageListener.java | 290 +++--- .../Client/TelegramClient.java | 37 +- .../Database/MessageDatabase.java | 319 +------ .../Server/ClientHandler.java | 874 ++---------------- .../UI/LoginController.java | 2 +- 6 files changed, 271 insertions(+), 1829 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 6870747..a483f1e 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -2,18 +2,22 @@ package org.to.telegramfinalproject.Client; import org.json.JSONArray; import org.json.JSONObject; +import org.to.telegramfinalproject.Database.PrivateChatDatabase; +import org.to.telegramfinalproject.Database.ContactDatabase; import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.SearchRequestModel; +import org.to.telegramfinalproject.Models.SearchResultModel; -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; public class ActionHandler { @@ -22,7 +26,6 @@ public class ActionHandler { private final Scanner scanner; public static volatile boolean forceExitChat = false; public static ActionHandler instance; - private final DataOutputStream outBin; //use for UI private volatile String lastStatus = "error"; // success | error @@ -54,12 +57,12 @@ public class ActionHandler { } } - public ActionHandler(PrintWriter out, BufferedReader in, DataOutputStream outBin, Scanner scanner) { + public ActionHandler(PrintWriter out, BufferedReader in, Scanner scanner) { this.out = out; this.in = in; - this.outBin = outBin; this.scanner = scanner; ActionHandler.instance = this; + } public void login(String username , String password){ @@ -542,12 +545,6 @@ public class ActionHandler { case "register": Session.currentUser = response.getJSONObject("data"); - //for downloaded medias - UUID accountId = UUID.fromString(Session.currentUser.getString("internal_uuid")); - Session.downloadsIndex = new DownloadsIndex(accountId); - - - JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list"); JSONArray Archived = Session.currentUser.getJSONArray("archived_chat_list"); JSONArray Active = Session.currentUser.getJSONArray("active_chat_list"); @@ -662,6 +659,7 @@ public class ActionHandler { + Session.activeChats = activeChats; Session.archivedChats = archivedChats; Session.chatList = chatList; @@ -751,7 +749,7 @@ public class ActionHandler { if (existing != null) { openChat(existing); - } else { + } else { ChatEntry preview = new ChatEntry(); preview.setId(String.valueOf(uuid)); preview.setDisplayId(selected.getString("id")); @@ -860,13 +858,13 @@ public class ActionHandler { - break; + break; case "create_group": case "create_channel": if (response.has("data")) { - JSONObject chatJson = response.getJSONObject("data"); + JSONObject chatJson = response.getJSONObject("data"); ChatEntry chat = new ChatEntry( UUID.fromString(chatJson.getString("internal_id")), @@ -881,12 +879,12 @@ public class ActionHandler { refreshChatList(); - System.out.println("✅ Created and opening chat..."); - refreshChatList(); - openChat(chat); - } + System.out.println("✅ Created and opening chat..."); + refreshChatList(); + openChat(chat); + } - break; + break; case "get_messages": JSONArray messages = response.getJSONObject("data").getJSONArray("messages"); @@ -1471,7 +1469,7 @@ public class ActionHandler { JSONObject m = messages.getJSONObject(i); String senderId = m.getString("sender_id"); String senderName = m.optString("sender_name", "Other"); - String content = m.optString("content", ""); + String content = m.getString("content"); String time = m.getString("send_at"); String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName; @@ -1693,7 +1691,7 @@ public class ActionHandler { String input = scanner.nextLine().trim(); switch (input) { - case "1" -> sendMessageInteractive(chatId, "private"); + case "1" -> sendMessage(chatId, "private"); case "2" -> { viewMessagesInChat(chat); } case "3" -> { return false; } default -> System.out.println("Invalid choice."); @@ -1712,7 +1710,7 @@ public class ActionHandler { String input = scanner.nextLine().trim(); switch (input) { - case "1" -> sendMessageInteractive(chat.getId(), "private"); + case "1" -> sendMessage(chatId, "private"); case "2" -> toggleBlock(chat.getOtherUserId()); case "3" -> { deleteChat(chatId, false); return true; } case "4" -> { deleteChat(chatId, true); return true; } @@ -1808,7 +1806,7 @@ public class ActionHandler { String input = scanner.nextLine(); switch (input) { - case "1" -> sendMessageInteractive(chat.getId(), "group"); + case "1" -> sendMessage(chat.getId(), "group"); case "2" -> viewGroupMembers(chat.getId()); case "3" -> { if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) @@ -1935,7 +1933,7 @@ public class ActionHandler { switch (input) { case "1" -> { if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { - sendMessageInteractive(chat.getId(), "channel"); + sendMessage(chat.getId(), "channel"); } else { System.out.println("❌ You don't have permission to post."); } @@ -3675,135 +3673,82 @@ public class ActionHandler { -// public void sendMessage(UUID chatId, String receiverType) { -// Scanner scanner = new Scanner(System.in); -// -// System.out.print("Enter your message: "); -// String content = scanner.nextLine(); -// -// System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): "); -// String messageType = scanner.nextLine().toUpperCase(); -// Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); -// while (!allowedTypes.contains(messageType)) { -// System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE): "); -// messageType = scanner.nextLine().toUpperCase(); -// } -// -// JSONArray attachmentsArray = new JSONArray(); -// System.out.print("Do you want to attach files? (yes/no): "); -// if (scanner.nextLine().equalsIgnoreCase("yes")) { -// while (true) { -// System.out.print("File URL: "); -// String fileUrl = scanner.nextLine(); -// System.out.print("File Type (IMAGE / VIDEO / FILE): "); -// String fileType = scanner.nextLine().toUpperCase(); -// -// JSONObject fileJson = new JSONObject(); -// fileJson.put("file_url", fileUrl); -// fileJson.put("file_type", fileType); -// attachmentsArray.put(fileJson); -// -// System.out.print("Add another file? (yes/no): "); -// if (!scanner.nextLine().equalsIgnoreCase("yes")) break; -// } -// } -// -// -// -// JSONObject messageJson = new JSONObject(); -// messageJson.put("action", "send_message"); -// messageJson.put("receiver_type", receiverType); -// messageJson.put("receiver_id", chatId.toString()); -// messageJson.put("content", content); -// messageJson.put("message_type", messageType); -// -// if (!attachmentsArray.isEmpty()) { -// messageJson.put("attachments", attachmentsArray); -// } -// -// JSONObject response = sendWithResponse(messageJson); -// if (response != null && response.getString("status").equals("success")) { -// System.out.println("✅ Message sent! ID: " + response.getJSONObject("data").getString("message_id")); -// } else { -// System.out.println("❌ Failed to send message: " + (response != null ? response.getString("message") : "No response")); -// } -// } + public void sendMessage(UUID chatId, String receiverType) { + Scanner scanner = new Scanner(System.in); + System.out.print("Enter your message: "); + String content = scanner.nextLine(); -// public void sendMessage(UUID chatId, String receiverType) { -// Scanner scanner = new Scanner(System.in); -// -// System.out.print("Enter your message (leave empty if file only): "); -// String content = scanner.nextLine(); -// -// System.out.print("Enter message type (TEXT / IMAGE / AUDIO / FILE / GIF): "); -// String messageType = scanner.nextLine().toUpperCase(); -// Set allowedTypes = Set.of("TEXT", "IMAGE", "AUDIO", "FILE", "GIF"); -// while (!allowedTypes.contains(messageType)) { -// System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / AUDIO / FILE / GIF): "); -// messageType = scanner.nextLine().toUpperCase(); -// } -// -// JSONArray attachmentsArray = new JSONArray(); -// System.out.print("Attach files? (yes/no): "); -// if (scanner.nextLine().equalsIgnoreCase("yes")) { -// while (true) { -// System.out.println("Paste the JSON you got from /upload (or leave empty to enter minimal fields):"); -// String jsonLine = scanner.nextLine().trim(); -// -// JSONObject fileJson; -// if (!jsonLine.isEmpty()) { -// // انتظار خروجی کامل /upload -// fileJson = new JSONObject(jsonLine); -// // اگه خروجی /upload تو ریشه‌ست، تبدیلش کن به ساختار attachment -// fileJson = new JSONObject() -// .put("file_url", fileJson.optString("file_url", "")) -// .put("file_type", fileJson.optString("file_type", "FILE")) -// .put("file_name", fileJson.optString("file_name", "")) -// .put("file_size", fileJson.optLong("file_size", 0)) -// .put("mime_type", fileJson.optString("mime_type", "")) -// .put("width", fileJson.isNull("width") ? JSONObject.NULL : fileJson.optInt("width")) -// .put("height", fileJson.isNull("height") ? JSONObject.NULL : fileJson.optInt("height")) -// .put("duration_seconds", fileJson.isNull("duration_seconds") ? JSONObject.NULL : fileJson.optInt("duration_seconds")) -// .put("thumbnail_url", fileJson.isNull("thumbnail_url") ? JSONObject.NULL : fileJson.optString("thumbnail_url", null)); -// } else { -// // ورودی حداقلی -// System.out.print("File URL: "); -// String fileUrl = scanner.nextLine(); -// System.out.print("File Type (IMAGE / AUDIO / FILE / GIF): "); -// String fileType = scanner.nextLine().toUpperCase(); -// -// fileJson = new JSONObject(); -// fileJson.put("file_url", fileUrl); -// fileJson.put("file_type", fileType); -// } -// -// attachmentsArray.put(fileJson); -// -// System.out.print("Add another file? (yes/no): "); -// if (!scanner.nextLine().equalsIgnoreCase("yes")) break; -// } -// } -// -// JSONObject messageJson = new JSONObject(); -// messageJson.put("action", "send_message"); -// messageJson.put("receiver_type", receiverType); // "private"/"group"/"channel" -// messageJson.put("receiver_id", chatId.toString()); // در private = chat_id -// messageJson.put("content", content); -// messageJson.put("message_type", messageType); -// if (attachmentsArray.length() > 0) { -// messageJson.put("attachments", attachmentsArray); -// } -// -// JSONObject response = sendWithResponse(messageJson); -// if (response != null && response.getString("status").equals("success")) { -// System.out.println("✅ Message sent! ID: " + response.getJSONObject("data").getString("message_id")); -// } else { -// System.out.println("❌ Failed to send message: " + (response != null ? response.optString("message","No message") : "No response")); -// } -// } -// - private void refreshContactList() { + System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); + String messageType = scanner.nextLine().toUpperCase(); + Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedTypes.contains(messageType)) { + System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE / AUDIO): "); + messageType = scanner.nextLine().toUpperCase(); + } + + JSONArray attachmentsArray = new JSONArray(); + System.out.print("Do you want to attach files? (yes/no): "); + if (scanner.nextLine().equalsIgnoreCase("yes")) { + while (true) { + System.out.print("File URL: "); + String fileUrl = scanner.nextLine(); + + // URL validation + if (fileUrl.isEmpty()) { + System.out.print("URL can not be empty. Try again."); + continue; + } + + if (fileUrl.contains(" ")) { + System.out.println("URL cannot contain spaces. Try again."); + continue; + } + + if (!fileUrl.isEmpty() && !fileUrl.matches("^(http|https)://.*$")) { + System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link."); + continue; + } + + System.out.print("File Type (IMAGE / VIDEO / FILE / AUDIO): "); + String fileType = scanner.nextLine().toUpperCase(); + + Set allowedFileTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedFileTypes.contains(fileType)) { + System.out.print("❌ Invalid type. Try again (IMAGE / VIDEO / FILE / AUDIO): "); + fileType = scanner.nextLine().toUpperCase(); + } + + JSONObject fileJson = new JSONObject(); + fileJson.put("file_url", fileUrl); + fileJson.put("file_type", fileType); + attachmentsArray.put(fileJson); + + System.out.print("Add another file? (yes/no): "); + if (!scanner.nextLine().equalsIgnoreCase("yes")) break; + } + } + + JSONObject messageJson = new JSONObject(); + messageJson.put("action", "send_message"); + messageJson.put("receiver_type", receiverType); + messageJson.put("receiver_id", chatId.toString()); + messageJson.put("content", content); + messageJson.put("message_type", messageType); + + if (!attachmentsArray.isEmpty()) { + messageJson.put("attachments", attachmentsArray); + } + + JSONObject response = sendWithResponse(messageJson); + if (response != null && response.getString("status").equals("success")) { + System.out.println("✅ Message sent! ID: " + response.getJSONObject("data").getString("message_id")); + } else { + System.out.println("❌ Failed to send message: " + (response != null ? response.getString("message") : "No response")); + } + } + + public void refreshContactList() { JSONObject req = new JSONObject(); req.put("action", "get_contact_list"); req.put("user_id", Session.currentUser.getString("user_id")); @@ -3915,18 +3860,6 @@ public class ActionHandler { replyLabel = "↪️ Reply to " + repliedSender + ": \"" + repliedContent + "\""; } - JSONArray atts = msg.optJSONArray("attachments"); - if (atts != null && atts.length() > 0) { - System.out.println(" 📎 " + atts.length() + " attachment(s)"); - for (int a = 0; a < atts.length(); a++) { - JSONObject att = atts.getJSONObject(a); - String fn = att.optString("file_name", "(unnamed)"); - long sz = att.optLong("file_size", 0); - System.out.printf(" - #%d %s (%s)\n", a + 1, fn, humanSize(sz)); - } - } - - JSONArray reactions = msg.optJSONArray("reactions"); if (reactions != null && !reactions.isEmpty()) { System.out.print(" 💬 Reactions: "); @@ -3961,7 +3894,7 @@ public class ActionHandler { } if(input.equalsIgnoreCase("S")){ - sendMessageInteractive(chat.getId(), chat.getType()); + sendMessage(chat.getId(), chat.getType()); } try { int index = Integer.parseInt(input); @@ -3976,10 +3909,6 @@ public class ActionHandler { boolean isSender = senderId.toString().equals(Session.currentUser.getString("internal_uuid")); boolean isChannel = chat.getType().equals("channel"); boolean isOwnerOrAdmin = chat.isOwner() || chat.isAdmin(); - //for media - JSONArray atts = selected.optJSONArray("attachments"); - boolean hasAttachments = (atts != null && atts.length() > 0); - System.out.println("\n🎯 Selected message by " + selected.getString("sender_name")); @@ -4005,9 +3934,6 @@ public class ActionHandler { System.out.println("5. Delete"); } } - if (hasAttachments) { - System.out.println("D. Download attachment"); - } System.out.println("0. Back to message list"); System.out.print("➤ Select an action: "); @@ -4040,15 +3966,6 @@ public class ActionHandler { System.out.println("❌ You are not allowed to delete this message."); } case "0" -> {} - - case "D", "d" -> { - if (hasAttachments) { - downloadAttachmentFlow(chat, selected); - } else { - System.out.println("🚫 No attachments to download."); - } - } - default -> System.out.println("❌ Invalid option."); } @@ -4059,122 +3976,6 @@ public class ActionHandler { } public void editMessage(UUID messageId) { - private void downloadAttachmentFlow(ChatEntry chat, JSONObject msg) { - JSONArray atts = msg.optJSONArray("attachments"); - if (atts == null || atts.length() == 0) { - System.out.println("🚫 No attachments."); - return; - } - - int idx = 0; - if (atts.length() > 1) { - System.out.print("Which attachment [1.." + atts.length() + "]? "); - try { - String ans = scanner.nextLine().trim(); - if (!ans.isEmpty()) { - int n = Integer.parseInt(ans); - if (n >= 1 && n <= atts.length()) idx = n - 1; - } - } catch (Exception ignored) { idx = 0; } - } - - JSONObject att = atts.getJSONObject(idx); - String mediaKeyStr = att.optString("media_key", ""); - if (mediaKeyStr.isBlank()) { - System.out.println("❌ Attachment missing media_key."); - return; - } - - UUID mediaKey = UUID.fromString(mediaKeyStr); - String rawName = att.optString("file_name", mediaKey.toString()); - String fileName = sanitizeFileName(rawName); - long declaredSize = att.optLong("file_size", 0L); - - // ~/Downloads/TeleSock/// - String accFolder = accountFolderName(); - String chatFolder = chatFolderName(chat); - Path saveDir = Paths.get(System.getProperty("user.home"), - "Downloads", "TeleSock", accFolder, chatFolder); - - System.out.println("👤 AccountFolder = " + accFolder); - System.out.println("💬 ChatFolder = " + chatFolder); - System.out.println("📁 SaveDir = " + saveDir); - - try { Files.createDirectories(saveDir); } - catch (IOException e) { - System.out.println("❌ Cannot create folder: " + saveDir + " -> " + e.getMessage()); - return; - } - - DownloadsIndex di = Session.downloadsIndex; - if (di != null) { - Path existing = di.find(mediaKey); - if (existing != null) { - System.out.println("✅ Already downloaded: " + existing); - return; - } - } - Path target = uniquePath(saveDir, fileName); - - TelegramClient.mediaBusy.set(true); - try { - Path saved = TelegramClient.getDownloader() - .download(mediaKey, saveDir, target.getFileName().toString()); - - long sizeToRecord = declaredSize > 0 ? declaredSize : Files.size(saved); - if (di != null) di.put(mediaKey, saved, sizeToRecord); - - System.out.println("✅ Saved to: " + saved + " (" + humanSize(sizeToRecord) + ")"); - } catch (Exception ex) { - System.out.println("❌ Download failed: " + ex.getMessage()); - } finally { - TelegramClient.mediaBusy.set(false); - } - } - - - - - private static Path uniquePath(Path dir, String fileName) { - Path p = dir.resolve(fileName); - if (!Files.exists(p)) return p; - - String name = fileName; - String ext = ""; - int dot = fileName.lastIndexOf('.'); - if (dot > 0 && dot < fileName.length()-1) { - name = fileName.substring(0, dot); - ext = fileName.substring(dot); // includes dot - } - int i = 1; - while (true) { - Path cand = dir.resolve(String.format("%s (%d)%s", name, i, ext)); - if (!Files.exists(cand)) return cand; - i++; - } - } - - private static String sanitizeFileName(String s) { - s = s.replace("\\", "/"); - if (s.contains("/")) s = s.substring(s.lastIndexOf('/') + 1); - s = s.replaceAll("[\\\\/:*?\"<>|]", "_"); - if (s.equals(".") || s.equals("..") || s.isBlank()) s = "file"; - return s; - } - - private static String humanSize(long b) { - if (b <= 0) return "0 B"; - String[] u = {"B","KB","MB","GB","TB"}; - int i = (int) Math.floor(Math.log(b) / Math.log(1024)); - if (i < 0) i = 0; - if (i >= u.length) i = u.length - 1; - double v = b / Math.pow(1024, i); - return String.format("%.1f %s", v, u[i]); - } - - - - private void editMessage(UUID messageId) { System.out.print("📝 Enter new content: "); String newContent = scanner.nextLine().trim(); @@ -4390,183 +4191,4 @@ public class ActionHandler { } - - public void sendMessageInteractive(UUID receiverId, String receiverType) { - Scanner sc = new Scanner(System.in); - - System.out.print("Type (TEXT / IMAGE / AUDIO): "); - String type = sc.nextLine().trim().toUpperCase(); - while (!Set.of("TEXT","IMAGE","AUDIO").contains(type)) { - System.out.print("❌ Invalid. Try (TEXT / IMAGE / AUDIO): "); - type = sc.nextLine().trim().toUpperCase(); - } - - System.out.print("Text (optional for media; empty = no caption): "); - String text = sc.nextLine(); - - if ("TEXT".equals(type)) { - sendTextMessage(receiverId, receiverType, text); - } else { - System.out.print("File path: "); - String path = sc.nextLine().trim(); - File f = new File(path); - if (!f.isFile()) { - System.out.println("❌ File not found"); - return; - } - try { - sendMediaMessage(receiverId, receiverType, type, f, text); - } catch (Exception e) { - e.printStackTrace(); - System.out.println("❌ Media send failed: " + e.getMessage()); - } - } - } - - private void sendTextMessage(UUID receiverId, String receiverType, String content) { - JSONObject req = new JSONObject() - .put("action", "send_message") - .put("receiver_type", receiverType) - .put("receiver_id", receiverId.toString()) - .put("message_type", "TEXT") - .put("content", content == null ? "" : content); - - JSONObject resp = sendWithResponse(req); - if (resp != null && "success".equalsIgnoreCase(resp.optString("status"))) { - System.out.println("✅ Sent. id=" + resp.optJSONObject("data").optString("message_id","")); - } else { - System.out.println("❌ Failed: " + (resp != null ? resp.optString("message") : "no response")); - } - } - - - public void sendMediaMessage(UUID receiverId, String receiverType, String type /* IMAGE/AUDIO */, File file, String caption) { - if (file == null) { - System.out.println("❌ File is null"); - return; - } - if (!file.exists()) { - System.out.println("❌ File not found: " + file.getAbsolutePath()); - return; - } - if (file.isDirectory()) { - System.out.println("❌ Path is a directory, expected a file: " + file.getAbsolutePath()); - return; - } - - final UUID messageId = UUID.randomUUID(); - - try { - String mime = detectMime(file, type.toUpperCase()); - if (mime == null) mime = type.equalsIgnoreCase("IMAGE") ? "image/*" : "audio/*"; - - JSONObject header = new JSONObject() - .put("message_id", messageId.toString()) - .put("sender_id", TelegramClient.loggedInUserId.toString()) - .put("receiver_type", receiverType) // private|group|channel - .put("receiver_id", receiverId.toString()) - .put("message_type", type.toUpperCase()) // IMAGE | AUDIO - .put("file_name", file.getName()) - .put("mime_type", mime) - .put("text", caption == null ? "" : caption); - - byte[] headerBytes = header.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); - long contentLen = file.length(); - - BlockingQueue q = new LinkedBlockingQueue<>(1); - TelegramClient.pendingResponses.put(messageId.toString(), q); - - try { - - outBin.write("MEDIA\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII)); - outBin.flush(); - - // 2) binary frame: magic + headerLen + header + contentLen + content - outBin.writeInt(0x4D444D31); // "MDM1" - outBin.writeInt(headerBytes.length); // headerLen (int) - outBin.write(headerBytes); // header - outBin.writeLong(contentLen); // contentLen (long) - - try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) { - byte[] buf = new byte[8192]; - int n; - while ((n = fis.read(buf)) != -1) { - outBin.write(buf, 0, n); - } - } - outBin.flush(); - - JSONObject ack = q.poll(20, java.util.concurrent.TimeUnit.SECONDS); - if (ack == null) { - System.out.println("❌ Media ACK timeout for " + messageId); - return; - } - - String status = ack.optString("status", "error"); - if ("success".equalsIgnoreCase(status)) { - System.out.println("✅ Media sent. id=" + ack.optString("message_id") + - " url=" + ack.optString("file_url")); - } else { - System.out.println("❌ Media failed: " + ack.optString("message")); - } - - } finally { - TelegramClient.pendingResponses.remove(messageId.toString()); - } - - } catch (Exception e) { - e.printStackTrace(); - System.out.println("❌ sendMediaMessage error: " + e.getMessage()); - } - } - - - private static String detectMime(File f, String typeUpper /* IMAGE or AUDIO */) { - try { - String m = java.nio.file.Files.probeContentType(f.toPath()); - if (m != null) return m; - } catch (Exception ignored) {} - String name = f.getName().toLowerCase(); - if (name.endsWith(".png")) return "image/png"; - if (name.endsWith(".jpg") || name.endsWith(".jpeg")) return "image/jpeg"; - if (name.endsWith(".gif")) return "image/gif"; - if (name.endsWith(".mp3")) return "audio/mpeg"; - if (name.endsWith(".wav")) return "audio/wav"; - if (name.endsWith(".ogg")) return "audio/ogg"; - return typeUpper.equals("IMAGE") ? "image/*" : "audio/*"; - } - - - - private static String safeName(String s) { - if (s == null) return "unknown"; - s = s.replace("\\", "/"); - if (s.contains("/")) s = s.substring(s.lastIndexOf('/') + 1); - s = s.replaceAll("[\\\\/:*?\"<>|]", "_").trim(); - if (s.isEmpty() || s.equals(".") || s.equals("..")) s = "unknown"; - return s; - } - - private static String accountFolderName() { - JSONObject me = Session.currentUser; - String acc = me.optString("username", - me.optString("user_id", - me.optString("profile_name", - me.optString("internal_uuid", "me")))); - return safeName(acc); - } - - private static String chatFolderName(ChatEntry chat) { - String name = chat.getName(); - if (name == null || name.isBlank()) { - name = chat.getDisplayId() != null && !chat.getDisplayId().isBlank() - ? chat.getDisplayId() - : String.valueOf(chat.getId()); - } - return safeName(name); - } - - -} - - +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 8f7a435..649cd9d 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -8,9 +8,6 @@ import org.to.telegramfinalproject.UI.MainController; import java.io.BufferedReader; import java.io.IOException; import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; -import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.Optional; import java.util.UUID; @@ -18,7 +15,6 @@ import java.util.concurrent.BlockingQueue; public class IncomingMessageListener implements Runnable { private final BufferedReader in; - private volatile boolean running = true; public enum UIMode { CONSOLE, UI } private final UIMode uiMode; // runtime mode @@ -40,47 +36,16 @@ public class IncomingMessageListener implements Runnable { try { System.out.println("👂 Real-Time Listener started."); - while (running) { - if (TelegramClient.mediaBusy.get()) { - try { Thread.sleep(15); } catch (InterruptedException ignored) {} - continue; - } + String line; + while ((line = in.readLine()) != null) { - if (!in.ready()) { - try { Thread.sleep(10); } catch (InterruptedException ignored) {} - continue; - } - - String line = in.readLine(); - if (line == null) { - break; - } - - if (line.isBlank()) continue; - - final JSONObject response; - try { - response = new JSONObject(line); - } catch (Exception badJson) { - System.out.println("⚠️ [Listener] Non-JSON line ignored: " + line); - continue; - } + JSONObject response = new JSONObject(line); System.out.println("📥 Received raw line: " + line); - // --- Media ACK routing by message_id --- - String mid = response.optString("message_id", ""); - if (!mid.isEmpty()) { - BlockingQueue q = TelegramClient.pendingResponses.get(mid); - if (q != null) { - q.put(response); - continue; - } - } - - // --- General request_id response routing --- + //if it has reqID answer if (response.has("request_id")) { - String requestId = response.optString("request_id", ""); + String requestId = response.getString("request_id"); System.out.println("📬 Response with request_id: " + requestId); System.out.println("📬 Full response: " + response.toString(2)); @@ -91,12 +56,15 @@ public class IncomingMessageListener implements Runnable { System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue..."); TelegramClient.responseQueue.put(response); } + continue; } - // --- Real-time actions --- + + + //if it has action check it if (response.has("action")) { - String action = response.optString("action", ""); + String action = response.getString("action"); System.out.println("🎯 [Listener] Action received: " + response.toString(2)); System.out.println("🎯 Received action: " + action); @@ -105,12 +73,11 @@ public class IncomingMessageListener implements Runnable { } else { TelegramClient.responseQueue.put(response); } + } else if (response.has("status") && response.has("message")) { - // General success/error - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // general answer } else { - // Fallback - TelegramClient.responseQueue.put(response); + TelegramClient.responseQueue.put(response); // fallback } } @@ -127,9 +94,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", - "admin_permissions_updated", "created_private_chat", - "message_reacted", "message_unreacted" , "chat_updated"-> true; + "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated","created_private_chat" , "message_reacted" , "message_unreacted","chat_updated" -> true; default -> false; }; } @@ -148,11 +113,9 @@ public class IncomingMessageListener implements Runnable { System.out.println("🔄 Chat list changed. Updating..."); Session.forceRefreshChatList = true; - String chatId = msg.optString("chat_id", ""); - String chatType = msg.optString("chat_type", ""); - if (!chatId.isBlank() && !chatType.isBlank()) { - ActionHandler.requestChatInfo(chatId, chatType); - } + 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..."); @@ -227,7 +190,7 @@ public class IncomingMessageListener implements Runnable { case "message_edited", "message_deleted_global", "message_reacted", "message_unreacted" - , "blocked_by_user", "unblocked_by_user", "message_seen" -> { + , "blocked_by_user", "unblocked_by_user", "message_seen" -> { displayRealTimeMessage(action, msg); } @@ -235,136 +198,78 @@ public class IncomingMessageListener implements Runnable { System.out.println("\n❓ Unknown real-time action: " + action); System.out.println(msg.toString(2)); } - default -> displayRealTimeMessage(action, msg); } System.out.print(">> "); } -// private void updateLastMessageTime(JSONObject msg) { -// try { -// UUID chatUUID = UUID.fromString(msg.getString("chat_id")); -// String newTime = msg.optString("last_message_time", null); -// -// Session.chatList.stream().filter(chat -> chat.getId().equals(chatUUID)).findFirst() -// .ifPresent(chat -> { -// chat.setLastMessageTime(newTime); -// System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); -// }); -// -// Session.activeChats.stream().filter(chat -> chat.getId().equals(chatUUID)).findFirst() -// .ifPresent(chat -> { -// chat.setLastMessageTime(newTime); -// System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); -// }); -// -// Session.archivedChats.stream().filter(chat -> chat.getId().equals(chatUUID)).findFirst() -// .ifPresent(chat -> { -// chat.setLastMessageTime(newTime); -// System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); -// }); -// -// Session.chatList.sort((c1, c2) -> { -// if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; -// if (c1.getLastMessageTime() == null) return 1; -// if (c2.getLastMessageTime() == null) return -1; -// return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); -// }); -// Session.activeChats.sort((c1, c2) -> { -// if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; -// if (c1.getLastMessageTime() == null) return 1; -// if (c2.getLastMessageTime() == null) return -1; -// return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); -// }); -// Session.archivedChats.sort((c1, c2) -> { -// if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; -// if (c1.getLastMessageTime() == null) return 1; -// if (c2.getLastMessageTime() == null) return -1; -// return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); -// }); -// -// if (Session.inChatListMenu) { -// ActionHandler.displayChatList(); -// System.out.print("Select a chat by number: "); -// } -// -// } catch (Exception e) { -// System.out.println("❌ Failed to update last message time: " + e.getMessage()); -// } -// } - - private void updateLastMessageTime(JSONObject msg) { try { UUID chatUUID = UUID.fromString(msg.getString("chat_id")); String newTime = msg.optString("last_message_time", null); - if (newTime == null || newTime.isBlank()) return; - updateOneList(Session.chatList, chatUUID, newTime); - updateOneList(Session.activeChats, chatUUID, newTime); - updateOneList(Session.archivedChats, chatUUID, newTime); + Session.chatList.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.activeChats.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.archivedChats.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.chatList.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + Session.activeChats.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + Session.archivedChats.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + + - sortByLastMessageTime(Session.chatList); - sortByLastMessageTime(Session.activeChats); - sortByLastMessageTime(Session.archivedChats); if (Session.inChatListMenu) { ActionHandler.displayChatList(); System.out.print("Select a chat by number: "); } + } catch (Exception e) { System.out.println("❌ Failed to update last message time: " + e.getMessage()); } } - private void updateOneList(List list, UUID chatUUID, String newTime) { - if (list == null) return; - for (ChatEntry chat : list) { - if (chatUUID.equals(chat.getId())) { // ✅ internal UUID - chat.setLastMessageTime(newTime); - System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId()); - break; - } - } - } - - private static void sortByLastMessageTime(java.util.List list) { - if (list == null) return; - list.sort((a, b) -> { - var ta = parseTs(String.valueOf(a.getLastMessageTime())); - var tb = parseTs(String.valueOf(b.getLastMessageTime())); - if (ta == null && tb == null) return 0; - if (ta == null) return 1; - if (tb == null) return -1; - return tb.compareTo(ta); - }); - - - for (int i = 0; i < list.size(); i++) { - if (list.get(i).isSavedMessages()) { - list.add(0, list.remove(i)); - break; - } - } - } - - private static java.time.LocalDateTime parseTs(String s) { - if (s == null) return null; - s = s.trim(); - if (s.isEmpty() || s.equalsIgnoreCase("null")) return null; - try { return java.time.OffsetDateTime.parse(s).toLocalDateTime(); } catch (Exception ignore) {} - try { return java.time.LocalDateTime.parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME); } catch (Exception ignore) {} - return null; - } - - private void handleAdminRoleChanged(JSONObject data) throws IOException { - String chatType = data.optString("chat_type", ""); - String chatId = data.optString("group_id", - data.optString("channel_id", data.optString("chat_id", ""))); + String chatType = data.getString("chat_type"); + String chatId = data.optString("group_id", data.optString("channel_id", data.optString("chat_id", null))); - if (chatId.isBlank()) { + if (chatId == null) { System.out.println("⚠️ No valid ID found in real-time data: " + data.toString(2)); return; } @@ -372,10 +277,11 @@ public class IncomingMessageListener implements Runnable { System.out.println("\n🔄 Your admin status changed. Updating chat info..."); try { - JSONObject chatInfoReq = new JSONObject() - .put("action", "get_chat_info") - .put("receiver_id", chatId) - .put("receiver_type", chatType); + // 1. get chat info + JSONObject chatInfoReq = new JSONObject(); + chatInfoReq.put("action", "get_chat_info"); + chatInfoReq.put("receiver_id", chatId); + chatInfoReq.put("receiver_type", chatType); System.out.println("📤 Sending get_chat_info: " + chatInfoReq); JSONObject chatInfoResp = ActionHandler.sendWithResponse(chatInfoReq); JSONObject chatData = chatInfoResp.getJSONObject("data"); @@ -401,17 +307,21 @@ public class IncomingMessageListener implements Runnable { Session.currentChatEntry = chat; }); + // 2. get permission JSONObject permissionReq = new JSONObject(); if (chatType.equalsIgnoreCase("group")) { - permissionReq.put("action", "get_group_permissions").put("group_id", chatId); + permissionReq.put("action", "get_group_permissions"); + permissionReq.put("group_id", chatId); } else { - permissionReq.put("action", "get_channel_permissions").put("channel_id", chatId); + permissionReq.put("action", "get_channel_permissions"); + permissionReq.put("channel_id", chatId); } JSONObject permissionResp = ActionHandler.sendWithResponse(permissionReq); JSONObject perm = permissionResp.getJSONObject("data"); entry.ifPresent(chat -> chat.setPermissions(perm)); + // 3. set currentChatId Session.currentChatId = chatUUID.toString(); System.out.println("🧪 Checking refresh conditions..."); @@ -434,11 +344,20 @@ public class IncomingMessageListener implements Runnable { } } + + + + + + + + + private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { String senderName = msg.optString("sender_name","Unknown"); - String content = msg.optString("content",""); + String content = msg.optString("content","(empty)"); String sendAt = msg.optString("send_at","-"); String chatId = msg.optString("receiver_id", msg.optString("chat_id","")); String kind = msg.optString("kind","plain"); @@ -457,47 +376,47 @@ public class IncomingMessageListener implements Runnable { Session.currentChatId != null && Session.currentChatId.equals(chatId); if (isInCurrentChat) { - if (content.isBlank()) content = "(no content)"; // برای مدیا بدون کپشن System.out.println(senderName + ": " + prefix + content + " (" + sendAt + ")"); } else { - String preview = content.isBlank() ? "[media]" : content; - System.out.println("💬 Message from " + senderName + ": " + prefix + preview); + System.out.println("💬 Message from " + senderName + ": " + prefix + content); Session.forceRefreshChatList = true; } } + case "message_edited" -> { System.out.println("\n✏️ Message Edited:"); - System.out.println("ID: " + msg.optString("message_id","")); - System.out.println("New Content: " + msg.optString("new_content","")); - System.out.println("Edit Time: " + msg.optString("edited_at","")); + 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_global" -> { System.out.println("\n🗑️ Message Deleted:"); - System.out.println("Message ID: " + msg.optString("message_id","")); + System.out.println("Message ID: " + msg.getString("message_id")); } case "message_reacted", "message_unreacted" -> { - String mid = msg.optString("message_id",""); - String emoji = msg.optString("emoji",""); + String mid = msg.getString("message_id"); + String emoji = msg.getString("emoji"); + JSONObject counts = msg.optJSONObject("counts"); int n = msg.optInt("count_for_emoji", 0); System.out.println("\n⭐ Reaction update on " + mid + " : " + emoji + " → " + n); } case "user_status_changed" -> { System.out.println("\n🔄 User Status Changed:"); - System.out.println("User: " + msg.optString("user_id","")); - System.out.println("Status: " + msg.optString("status","")); + System.out.println("User: " + msg.getString("user_id")); + System.out.println("Status: " + msg.getString("status")); } case "blocked_by_user" -> { - System.out.println("\n⛔ You were blocked by user: " + msg.optString("blocker_id","")); + 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.optString("unblocker_id","")); + 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.optString("message_id","")); - System.out.println("Seen at: " + msg.optString("seen_at","")); + System.out.println("Message ID: " + msg.getString("message_id")); + System.out.println("Seen at: " + msg.getString("seen_at")); } default -> { System.out.println("\n❓ Unknown real-time action: " + action); @@ -508,9 +427,6 @@ public class IncomingMessageListener implements Runnable { - - - private static LocalDateTime parseIsoFlexible(String iso) { if (iso == null || iso.isBlank()) return null; try { return LocalDateTime.parse(iso); } catch (Exception ignore) {} @@ -562,4 +478,4 @@ public class IncomingMessageListener implements Runnable { } catch (Exception e) { System.err.println("[RT] bumpChatListFromMessage: " + e.getMessage()); } } -} +} \ 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 dec1eaf..62581a1 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -128,7 +128,10 @@ package org.to.telegramfinalproject.Client; import org.json.JSONObject; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -137,11 +140,9 @@ import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; public class TelegramClient { private static final String SERVER_HOST = "localhost"; - private static final int SERVER_PORT = 8080; private static final int SERVER_PORT = 8000; private static TelegramClient instance; @@ -157,13 +158,6 @@ public class TelegramClient { public static final BlockingQueue responseQueue = new LinkedBlockingQueue<>(); public static final Map> pendingResponses = new ConcurrentHashMap<>(); public static UUID loggedInUserId = null; - public static final Map> pendingResponses = new ConcurrentHashMap<>(); - private DataInputStream inBin; // NEW - private static SocketMediaDownloader downloader; // NEW - public static final AtomicBoolean mediaBusy = new AtomicBoolean(false); // - private DownloadsIndex downloadIndex; - - private static TelegramClient instance; private volatile boolean listenerStarted = false; @@ -174,14 +168,8 @@ public class TelegramClient { public static synchronized TelegramClient getInstance() { if (instance == null) instance = new TelegramClient(); - public static SocketMediaDownloader getDownloader() { - return downloader; - } - - public static TelegramClient getInstance() { return instance; } - private DataOutputStream outBin; // public void startConsole() { // try { @@ -196,18 +184,6 @@ public class TelegramClient { public void startConsole() { try { - socket = new Socket(SERVER_HOST, SERVER_PORT); - InputStream rawIn = socket.getInputStream(); - OutputStream rawOut = socket.getOutputStream(); - in = new BufferedReader(new InputStreamReader(rawIn, StandardCharsets.UTF_8)); - out = new PrintWriter(new OutputStreamWriter(rawOut, StandardCharsets.UTF_8), true); - - inBin = new DataInputStream(rawIn); - outBin = new DataOutputStream(rawOut); - downloader = new SocketMediaDownloader(out, inBin, outBin); - - System.out.println("✅ Connected to Telegram Server"); - handler = new ActionHandler(out, in, outBin, scanner); connectIfNeeded(); initHandlerIfNeeded(); startListenerOnce(IncomingMessageListener.UIMode.CONSOLE); // ← کنسول @@ -293,7 +269,7 @@ public class TelegramClient { UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); loggedInUserId = internalId; - this.downloadIndex = DownloadIndexRegistry.forAccount(internalId); + handler.userMenu(internalId); } else { System.out.println("❌ Login failed."); @@ -334,5 +310,4 @@ public class TelegramClient { return listener; } -} - +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index aa4ef9b..a377ab4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -1,9 +1,7 @@ package org.to.telegramfinalproject.Database; import org.to.telegramfinalproject.Models.FileAttachment; -import org.to.telegramfinalproject.Models.MediaRow; import org.to.telegramfinalproject.Models.Message; -import org.to.telegramfinalproject.Utils.ChannelPermissionUtil; import java.sql.*; import java.time.LocalDateTime; @@ -68,134 +66,6 @@ public class MessageDatabase { } } - - public static boolean insertMessageTx(Connection conn, UUID messageId, UUID senderId, UUID receiverId, - String receiverType, String content, String messageType) throws SQLException { - String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type) " + - "VALUES (?, ?, ?, ?, ?, ?)"; - try (PreparedStatement ps = conn.prepareStatement(sql)) { - ps.setObject(1, messageId); - ps.setObject(2, senderId); - ps.setString(3, receiverType); - ps.setObject(4, receiverId); - if (content == null || content.isBlank()) ps.setNull(5, java.sql.Types.VARCHAR); else ps.setString(5, content); - ps.setString(6, messageType); - return ps.executeUpdate() > 0; - } - } - - public static boolean insertAttachmentsTx(Connection conn, UUID messageId, List attachments) throws SQLException { - if (attachments == null || attachments.isEmpty()) return true; - - final String sql = """ - INSERT INTO message_attachments( - attachment_id, message_id, - file_url, file_type, file_name, file_size, mime_type, - width, height, duration_seconds, thumbnail_url, - media_key, storage_path - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) - """; - - try (PreparedStatement ps = conn.prepareStatement(sql)) { - for (FileAttachment att : attachments) { - if (att == null) throw new IllegalArgumentException("Attachment is null"); - UUID attachmentId = att.getAttachmentId() != null ? att.getAttachmentId() : UUID.randomUUID(); - UUID mediaKey = att.getMediaKey() != null ? att.getMediaKey() : attachmentId; // ساده‌ترین حالت - - String ft = att.getFileType(); - if (!"IMAGE".equalsIgnoreCase(ft) && !"AUDIO".equalsIgnoreCase(ft)) { - throw new IllegalArgumentException("file_type must be IMAGE or AUDIO"); - } - if (att.getStoragePath() == null || att.getStoragePath().isBlank()) { - throw new IllegalArgumentException("storage_path is required for socket downloads"); - } - - int i = 1; - ps.setObject(i++, attachmentId); - ps.setObject(i++, messageId); - //file url (display link) - if (att.getFileUrl() == null || att.getFileUrl().isBlank()) ps.setNull(i++, java.sql.Types.VARCHAR); - else ps.setString(i++, att.getFileUrl()); - - ps.setString(i++, ft.toUpperCase()); - ps.setString(i++, att.getFileName()); - if (att.getFileSize() == null) ps.setNull(i++, java.sql.Types.BIGINT); else ps.setLong(i++, att.getFileSize()); - if (att.getMimeType() == null) ps.setNull(i++, java.sql.Types.VARCHAR); else ps.setString(i++, att.getMimeType()); - if (att.getWidth() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getWidth()); - if (att.getHeight() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getHeight()); - if (att.getDurationSeconds() == null) ps.setNull(i++, java.sql.Types.INTEGER); else ps.setInt(i++, att.getDurationSeconds()); - if (att.getThumbnailUrl() == null || att.getThumbnailUrl().isBlank()) ps.setNull(i++, java.sql.Types.VARCHAR); - else ps.setString(i++, att.getThumbnailUrl()); - - ps.setObject(i++, mediaKey); - ps.setString(i++, att.getStoragePath()); - - ps.addBatch(); - - att.setAttachmentId(attachmentId); - att.setMediaKey(mediaKey); - } - ps.executeBatch(); - return true; - } - } - - - - public static boolean saveMessageWithOptionalAttachments( - UUID messageId, UUID senderId, UUID receiverId, - String receiverType, String content, String messageType, - List attachments - ) { - Connection conn = null; - try { - conn = ConnectionDb.connect(); - conn.setAutoCommit(false); - - boolean isText = "TEXT".equalsIgnoreCase(messageType); - boolean isImage = "IMAGE".equalsIgnoreCase(messageType); - boolean isAudio = "AUDIO".equalsIgnoreCase(messageType); - if (!isText && !isImage && !isAudio) { - throw new IllegalArgumentException("messageType must be TEXT, IMAGE, or AUDIO"); - } - - if (isText) { - if (attachments != null && !attachments.isEmpty()) - throw new IllegalArgumentException("TEXT must not have attachments"); - if (content == null || content.isBlank()) - throw new IllegalArgumentException("TEXT must have non-empty content"); - } else { - if (attachments == null || attachments.isEmpty()) - throw new IllegalArgumentException("Non-TEXT must have at least one attachment"); - - for (FileAttachment a : attachments) { - if (a == null) throw new IllegalArgumentException("Attachment is null"); - String ft = a.getFileType(); - if (isImage && !"IMAGE".equalsIgnoreCase(ft)) - throw new IllegalArgumentException("All attachments must be IMAGE for messageType=IMAGE"); - if (isAudio && !"AUDIO".equalsIgnoreCase(ft)) - throw new IllegalArgumentException("All attachments must be AUDIO for messageType=AUDIO"); - } - } - - insertMessageTx(conn, messageId, senderId, receiverId, receiverType, content, messageType.toUpperCase()); - if (!isText) insertAttachmentsTx(conn, messageId, attachments); - - conn.commit(); - return true; - } catch (Exception e) { - if (conn != null) try { conn.rollback(); } catch (SQLException ignored) {} - e.printStackTrace(); - return false; - } finally { - if (conn != null) { - try { conn.setAutoCommit(true); } catch (SQLException ignored) {} - try { conn.close(); } catch (SQLException ignored) {} - } - } - } - - public static void markGloballyDeleted(UUID chatId) { String sql = "UPDATE messages SET is_deleted_globally = true WHERE receiver_id = ? AND receiver_type = 'private'"; try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { @@ -684,34 +554,30 @@ public class MessageDatabase { public static List getAttachments(UUID messageId) { List attachments = new ArrayList<>(); - String sql = "SELECT file_url, file_type, file_name, file_size, mime_type, width, height, duration_seconds, thumbnail_url " + - "FROM message_attachments WHERE message_id = ? ORDER BY uploaded_at"; + String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, messageId); ResultSet rs = stmt.executeQuery(); + while (rs.next()) { attachments.add(new FileAttachment( rs.getString("file_url"), - rs.getString("file_type"), - rs.getString("file_name"), - (Long) rs.getObject("file_size"), - rs.getString("mime_type"), - (Integer) rs.getObject("width"), - (Integer) rs.getObject("height"), - (Integer) rs.getObject("duration_seconds"), - rs.getString("thumbnail_url") + rs.getString("file_type") )); } + } catch (SQLException e) { e.printStackTrace(); } + return attachments; } - public static LocalDateTime getLastMessageTimeBetween(UUID user1, UUID user2, String type) { String sql = """ SELECT MAX(send_at) FROM messages @@ -779,7 +645,7 @@ public class MessageDatabase { (UUID) rs.getObject("forwarded_from"), rs.getBoolean("is_deleted_globally"), (rs.getTimestamp("edited_at") != null) ? rs.getTimestamp("edited_at").toLocalDateTime() : null - ); + ); } @@ -1297,173 +1163,6 @@ public class MessageDatabase { } -// -// public static MediaRow findMediaByKey(UUID mediaKey) throws SQLException { -// final String sql = """ -// SELECT a.message_id, a.storage_path, a.file_name, a.mime_type, a.file_size, -// m.receiver_type, m.receiver_id, m.sender_id -// FROM message_attachments a -// JOIN messages m ON m.message_id = a.message_id -// WHERE a.media_key = ? -// """; -// try (Connection c = ConnectionDb.connect(); -// PreparedStatement ps = c.prepareStatement(sql)) { -// ps.setObject(1, mediaKey); -// try (ResultSet rs = ps.executeQuery()) { -// if (!rs.next()) return null; -// MediaRow mr = new MediaRow(); -// mr.messageId = (UUID) rs.getObject(1); -// mr.storagePath = rs.getString(2); -// mr.fileName = rs.getString(3); -// mr.mimeType = rs.getString(4); -// mr.fileSize = rs.getLong(5); -// mr.receiverType= rs.getString(6); -// mr.receiverId = (UUID) rs.getObject(7); -// mr.senderId = (UUID) rs.getObject(8); -// return mr; -// } -// } -// } - -// public static boolean canAccess(UUID requester, MediaRow mr) { -// if ("private".equals(mr.receiverType)) { -// return requester.equals(mr.senderId) || requester.equals(mr.receiverId); -// } else if ("group".equals(mr.receiverType)) { -// return GroupDatabase.isMember(mr.receiverId, requester); -// } else if ("channel".equals(mr.receiverType)) { -// return ChannelDatabase.isUserInChannel(mr.receiverId, requester); -// } -// return false; -// } - - public static Map> findAttachmentsForMessages(List ids) throws SQLException { - Map> map = new java.util.HashMap<>(); - if (ids == null || ids.isEmpty()) return map; - - // ساخت IN به‌صورت امن - String placeholders = ids.stream().map(x -> "?").collect(java.util.stream.Collectors.joining(",")); - String sql = """ - SELECT attachment_id, message_id, media_key, file_name, file_size, mime_type, file_type, - width, height, duration_seconds, thumbnail_url, file_url, storage_path - FROM message_attachments - WHERE message_id IN (""" + placeholders + ") ORDER BY uploaded_at ASC"; - - try (Connection c = ConnectionDb.connect(); - PreparedStatement ps = c.prepareStatement(sql)) { - int i = 1; - for (UUID id : ids) ps.setObject(i++, id); - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - MediaRow a = new MediaRow(); - a.attachmentId = (UUID) rs.getObject("attachment_id"); - a.messageId = (UUID) rs.getObject("message_id"); - a.mediaKey = (UUID) rs.getObject("media_key"); - a.fileName = rs.getString("file_name"); - long sz = rs.getLong("file_size"); - a.fileSize = rs.wasNull() ? null : sz; - a.mimeType = rs.getString("mime_type"); - a.fileType = rs.getString("file_type"); - int w = rs.getInt("width"); - a.width = rs.wasNull() ? null : w; - int h = rs.getInt("height"); - a.height = rs.wasNull() ? null : h; - int d = rs.getInt("duration_seconds"); - a.durationSeconds = rs.wasNull() ? null : d; - a.thumbnailUrl = rs.getString("thumbnail_url"); - a.fileUrl = rs.getString("file_url"); - a.storagePath = rs.getString("storage_path"); - - map.computeIfAbsent(a.messageId, k -> new java.util.ArrayList<>()).add(a); - } - } - } - return map; - } - - - - - - public static MediaRow findMediaByKey(UUID mediaKey) throws SQLException { - String sql = """ - SELECT - ma.attachment_id, - ma.message_id, - ma.media_key, - ma.file_name, - ma.file_size, - ma.mime_type, - ma.file_type, - ma.width, - ma.height, - ma.duration_seconds, - ma.thumbnail_url, - ma.file_url, - ma.storage_path, - m.receiver_type, - m.receiver_id, - m.sender_id - FROM message_attachments ma - JOIN messages m ON m.message_id = ma.message_id - WHERE ma.media_key = ? - LIMIT 1 - """; - - try (Connection c = ConnectionDb.connect(); - PreparedStatement ps = c.prepareStatement(sql)) { - ps.setObject(1, mediaKey); - try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) return null; - - MediaRow a = new MediaRow(); - a.attachmentId = (UUID) rs.getObject("attachment_id"); - a.messageId = (UUID) rs.getObject("message_id"); - a.mediaKey = (UUID) rs.getObject("media_key"); - a.fileName = rs.getString("file_name"); - - long sz = rs.getLong("file_size"); - a.fileSize = rs.wasNull() ? null : sz; // MediaRow.fileSize = Long - - a.mimeType = rs.getString("mime_type"); - a.fileType = rs.getString("file_type"); - int w = rs.getInt("width"); a.width = rs.wasNull() ? null : w; - int h = rs.getInt("height"); a.height = rs.wasNull() ? null : h; - int d = rs.getInt("duration_seconds"); a.durationSeconds = rs.wasNull() ? null : d; - a.thumbnailUrl = rs.getString("thumbnail_url"); - a.fileUrl = rs.getString("file_url"); - a.storagePath = rs.getString("storage_path"); - a.receiverType = rs.getString("receiver_type"); - a.receiverId = (UUID) rs.getObject("receiver_id"); - a.senderId = (UUID) rs.getObject("sender_id"); - return a; - } - } - } - - - public static boolean canAccess(UUID requester, MediaRow mr) { - if (requester == null || mr == null || mr.receiverType == null) return false; - - // اختیاری: فرستنده همیشه مجاز - if (requester.equals(mr.senderId)) return true; - - switch (mr.receiverType.toLowerCase(Locale.ROOT)) { - case "private": - // receiver_id در پیام‌های private = UUID چت خصوصی - return PrivateChatDatabase.isParticipant(mr.receiverId, requester); - - case "group": - return GroupDatabase.isMember(mr.receiverId, requester); - - case "channel": - return ChannelPermissionUtil.isUserInChannel(requester, mr.receiverId); - - default: - return false; - } - } - - public static Message getLastMessage(UUID targetId, String type) { final String sql = "SELECT * FROM messages " + @@ -1640,4 +1339,4 @@ public class MessageDatabase { -} +} \ 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 21c9754..ffe0c97 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -12,10 +12,6 @@ import org.to.telegramfinalproject.Utils.GroupPermissionUtil; import java.io.*; import java.net.Socket; import java.sql.Connection; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.LocalDateTime; import java.util.*; @@ -24,15 +20,6 @@ public class ClientHandler implements Runnable { private final AuthService authService = new AuthService(); private User currentUser; - // ClientHandler.java - private static void log(String msg) { - System.out.println(java.time.LocalDateTime.now() + " [ClientHandler] " + msg); - } - private static void logf(String fmt, Object... args) { - log(String.format(fmt, args)); - } - - public ClientHandler(Socket socket) { this.socket = socket; @@ -43,60 +30,11 @@ public class ClientHandler implements Runnable { UUID userId = null; try ( - -// InputStream rawIn = socket.getInputStream(); -// OutputStream rawOut = socket.getOutputStream(); -// -// BufferedReader in = new BufferedReader(new InputStreamReader(rawIn, java.nio.charset.StandardCharsets.UTF_8)); -// PrintWriter out = new PrintWriter(new OutputStreamWriter(rawOut, java.nio.charset.StandardCharsets.UTF_8), true); - -// DataInputStream dis = new DataInputStream(rawIn); -// DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(rawOut)); - -// BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); -// PrintWriter out = new PrintWriter(socket.getOutputStream(), true) -// BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); - // DataInputStream dis = new DataInputStream(bis); //for binary headers - - //PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), java.nio.charset.StandardCharsets.UTF_8), true); - InputStream rawIn = socket.getInputStream(); - OutputStream rawOut = socket.getOutputStream(); - - BufferedInputStream bis = new BufferedInputStream(rawIn); - BufferedOutputStream bos = new BufferedOutputStream(rawOut); - - DataInputStream dis = new DataInputStream(bis); - DataOutputStream dos = new DataOutputStream(bos); - PrintWriter out = new PrintWriter(new OutputStreamWriter(bos, java.nio.charset.StandardCharsets.UTF_8), true); - + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter out = new PrintWriter(socket.getOutputStream(), true) ) { - -// DataInputStream bin = new DataInputStream(new BufferedInputStream(socket.getInputStream())); - String inputLine; - while ((inputLine = readUtf8Line(bis)) != null) { - - String line = inputLine.trim(); - - if ("MEDIA".equalsIgnoreCase(inputLine.trim())) { - handleMediaFrame(dis, out); - continue; - } - - - if ("MEDIA_DL".equalsIgnoreCase(line)) { - UUID cu = (currentUser == null ? null : currentUser.getInternal_uuid()); - logf("MEDIA_DL received. currentUser.internal_uuid=%s", cu); - - if (cu == null) { - log("MEDIA_DL rejected: currentUser is null or no internal_uuid"); - sendDlErr(dos, "not authorized"); - continue; - } - handleMediaDownload(dis, dos, cu); - continue; - } - + while ((inputLine = in.readLine()) != null) { JSONObject requestJson = new JSONObject(inputLine); String action = requestJson.getString("action"); ResponseModel response = null; @@ -208,7 +146,7 @@ public class ClientHandler implements Runnable { LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private"); - ChatEntry entry = new ChatEntry( + ChatEntry entry = new ChatEntry( chat.getChat_id(), // internal_id = chat_id isSelf ? "Saved Messages" : otherUser.getUser_id(), // id/display isSelf ? "Saved Messages" : otherUser.getProfile_name(), // name @@ -1716,7 +1654,7 @@ public class ClientHandler implements Runnable { } String chatType = requestJson.getString("chat_type"); UUID chatId = UUID.fromString(requestJson.getString("chat_id")); - userId = UUID.fromString(requestJson.getString("user_id")); + userId = UUID.fromString(requestJson.getString("user_id")); boolean success = false; @@ -1785,7 +1723,7 @@ public class ClientHandler implements Runnable { } try { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); - userId = currentUser.getInternal_uuid(); + userId = currentUser.getInternal_uuid(); JSONObject permissions = ChannelDatabase.getChannelPermissions(channelId, userId); @@ -2219,7 +2157,7 @@ public class ClientHandler implements Runnable { } case "send_message" : { - response = handleSendMessage(requestJson); + response = handleSendMessage(requestJson); } break; @@ -2337,13 +2275,6 @@ public class ClientHandler implements Runnable { List messages = MessageDatabase.getMessagesForChat(chatId, chatType, currentUser.getInternal_uuid(), offset, limit); - java.util.List mids = new java.util.ArrayList<>(); - for (Message m : messages) mids.add(m.getMessage_id()); - - // ⬅️ همهٔ اتچمنت‌ها را یک‌جا بگیر: message_id -> list(attachments) - java.util.Map> attMap = - MessageDatabase.findAttachmentsForMessages(mids); - JSONArray result = new JSONArray(); for (Message m : messages) { JSONObject obj = new JSONObject(); @@ -2411,26 +2342,6 @@ public class ClientHandler implements Runnable { obj.put("reactions", new JSONArray(reactions)); - JSONArray atts = new JSONArray(); - java.util.List list = attMap.getOrDefault(m.getMessage_id(), java.util.Collections.emptyList()); - for (MediaRow a : list) { - JSONObject aj = new JSONObject() - .put("media_key", a.mediaKey != null ? a.mediaKey.toString() : JSONObject.NULL) - .put("file_name", a.fileName != null ? a.fileName : JSONObject.NULL) - .put("file_size", a.fileSize != null ? a.fileSize : JSONObject.NULL) - .put("mime_type", a.mimeType != null ? a.mimeType : JSONObject.NULL) - .put("file_type", a.fileType != null ? a.fileType : JSONObject.NULL) - .put("width", a.width != null ? a.width : JSONObject.NULL) - .put("height", a.height != null ? a.height : JSONObject.NULL) - .put("duration_seconds", a.durationSeconds != null ? a.durationSeconds : JSONObject.NULL) - .put("thumbnail_url", a.thumbnailUrl != null ? a.thumbnailUrl : JSONObject.NULL) - // اختیاری/دیباگ - .put("file_url", a.fileUrl != null ? a.fileUrl : JSONObject.NULL); - atts.put(aj); - } - obj.put("attachments", atts); - - result.put(obj); } @@ -2765,7 +2676,7 @@ public class ClientHandler implements Runnable { } case "get_blocked_users": { - userId = currentUser.getInternal_uuid(); + userId = currentUser.getInternal_uuid(); var list = ContactDatabase.getBlockedUsers(userId); org.json.JSONObject data = new org.json.JSONObject(); data.put("blocked_users", list); @@ -2774,7 +2685,7 @@ public class ClientHandler implements Runnable { } case "verify_password": { - userId = currentUser.getInternal_uuid(); + userId = currentUser.getInternal_uuid(); String cur = requestJson.getString("current_password"); User user = userDatabase.findByInternalUUID(userId); boolean ok = PasswordHashing.verify(cur, user.getPassword()); @@ -2785,7 +2696,7 @@ public class ClientHandler implements Runnable { } case "update_username": { - userId = currentUser.getInternal_uuid(); + userId = currentUser.getInternal_uuid(); String cur = requestJson.getString("current_password"); String newUsername = requestJson.getString("new_username"); boolean useBCrypt = true; @@ -2806,7 +2717,7 @@ public class ClientHandler implements Runnable { } case "update_password": { - userId = currentUser.getInternal_uuid(); + userId = currentUser.getInternal_uuid(); String cur = requestJson.getString("current_password"); String newPass = requestJson.getString("new_password"); @@ -2959,9 +2870,7 @@ public class ClientHandler implements Runnable { userDatabase.updateLastSeen(userId); SessionManager.removeUser(userId); } - } catch (SQLException e) { - throw new RuntimeException(e); - } finally { + } finally { try { if (currentUser != null) { //RealTime @@ -2986,606 +2895,9 @@ public class ClientHandler implements Runnable { } - private static String readUtf8Line(BufferedInputStream bis) throws java.io.IOException { - StringBuilder sb = new StringBuilder(); - while (true) { - int b = bis.read(); - if (b == -1) { - return sb.length() == 0 ? null : sb.toString(); - } - if (b == '\n') { - int len = sb.length(); - if (len > 0 && sb.charAt(len - 1) == '\r') sb.setLength(len - 1); - return sb.toString(); - } - sb.append((char) b); - } - } - - -// private void handleMediaFrame(DataInputStream dis, PrintWriter out) { -// try { -// // MAGIC = "MDM1" -// final int MAGIC_EXPECTED = 0x4D444D31; -// int magic = dis.readInt(); -// if (magic != MAGIC_EXPECTED) { -// out.println(new JSONObject().put("status","error").put("message","bad magic").toString()); -// out.flush(); -// return; -// } -// -// int headerLen = dis.readInt(); -// if (headerLen <= 0 || headerLen > (64 * 1024)) { -// out.println(new JSONObject().put("status","error").put("message","bad header length").toString()); -// out.flush(); -// return; -// } -// -// byte[] headerBytes = dis.readNBytes(headerLen); -// if (headerBytes.length != headerLen) { -// out.println(new JSONObject().put("status","error").put("message","header truncated").toString()); -// out.flush(); -// return; -// } -// JSONObject h = new JSONObject(new String(headerBytes, java.nio.charset.StandardCharsets.UTF_8)); -// -// long contentLen = dis.readLong(); -// long MAX_MEDIA = 25L * 1024 * 1024; -// if (contentLen <= 0 || contentLen > MAX_MEDIA) { -// skip(dis, contentLen); -// out.println(new JSONObject().put("status","error").put("message","file too large/invalid").toString()); -// out.flush(); -// return; -// } -// -// UUID messageId = UUID.fromString(h.getString("message_id")); -// UUID senderId = UUID.fromString(h.getString("sender_id")); -// String rType = h.getString("receiver_type"); // private/group/channel -// UUID receiverId = UUID.fromString(h.getString("receiver_id")); -// String messageType = h.getString("message_type"); // IMAGE | AUDIO -// -// if (!"IMAGE".equalsIgnoreCase(messageType) && !"AUDIO".equalsIgnoreCase(messageType)) { -// skip(dis, contentLen); -// out.println(new JSONObject().put("status","error").put("message","unsupported message_type").toString()); -// out.flush(); -// return; -// } -// -// String fileName = h.optString("file_name", "file.bin"); -// String mimeType = h.optString("mime_type", "application/octet-stream"); -// String text = h.optString("text", ""); -// -// Integer width = h.has("width") && !h.isNull("width") ? h.getInt("width") : null; -// Integer height = h.has("height") && !h.isNull("height") ? h.getInt("height") : null; -// -// if (fileName.length() > 200) fileName = fileName.substring(0, 200); -// -// // مسیر ذخیره -// java.nio.file.Path baseDir = java.nio.file.Paths.get("uploads").toAbsolutePath().normalize(); -// java.nio.file.Files.createDirectories(baseDir); -// String kind = "IMAGE".equalsIgnoreCase(messageType) ? "images" : "audios"; -// String subdir = kind + "/" + java.time.LocalDate.now(); -// java.nio.file.Path dir = baseDir.resolve(subdir).normalize(); -// java.nio.file.Files.createDirectories(dir); -// -// String ext = guessExt(fileName, mimeType); -// String storedName = java.util.UUID.randomUUID() + ext; -// java.nio.file.Path target = dir.resolve(storedName).normalize(); -// -// // دریافت بایت‌های فایل -// try (OutputStream fos = new BufferedOutputStream(java.nio.file.Files.newOutputStream( -// target, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING))) { -// long remaining = contentLen; -// byte[] buf = new byte[8192]; -// while (remaining > 0) { -// int toRead = (int) Math.min(buf.length, remaining); -// int n = dis.read(buf, 0, toRead); -// if (n == -1) throw new EOFException("stream ended early"); -// fos.write(buf, 0, n); -// remaining -= n; -// } -// } -// -// long fileSize = java.nio.file.Files.size(target); -// String fileUrl = "/" + subdir.replace('\\','/') + "/" + storedName; -// -// FileAttachment att = new FileAttachment( -// fileUrl, -// messageType.toUpperCase(), // IMAGE/AUDIO -// fileName, -// fileSize, -// mimeType, -// width, -// height, -// null, // durationSeconds -// null // thumbnailUrl -// ); -// -// boolean ok = MessageDatabase.saveMessageWithOptionalAttachments( -// messageId, senderId, receiverId, rType, text, messageType.toUpperCase(), java.util.List.of(att) -// ); -// -// JSONObject ack = new JSONObject() -// .put("status", ok ? "success" : "error") -// .put("message_id", messageId.toString()) -// .put("file_url", fileUrl) -// .put("file_size", fileSize) -// .put("mime_type", mimeType); -// -// out.println(ack.toString()); -// out.flush(); -// -// } catch (Exception e) { -// e.printStackTrace(); -// out.println(new JSONObject().put("status","error").put("message","exception").toString()); -// out.flush(); -// } -// } - - - private void handleMediaFrame(DataInputStream dis, PrintWriter out) { - try { - final int MAGIC_EXPECTED = 0x4D444D31; // "MDM1" - int magic = dis.readInt(); - if (magic != MAGIC_EXPECTED) { - out.println(new JSONObject().put("status","error").put("message","bad magic").toString()); out.flush(); return; - } - - int headerLen = dis.readInt(); - if (headerLen <= 0 || headerLen > 64 * 1024) { - out.println(new JSONObject().put("status","error").put("message","bad header length").toString()); out.flush(); return; - } - - byte[] headerBytes = dis.readNBytes(headerLen); - if (headerBytes.length != headerLen) { - out.println(new JSONObject().put("status","error").put("message","header truncated").toString()); out.flush(); return; - } - - JSONObject h = new JSONObject(new String(headerBytes, java.nio.charset.StandardCharsets.UTF_8)); - - long contentLen = dis.readLong(); - long MAX_MEDIA = 25L * 1024 * 1024; - if (contentLen <= 0 || contentLen > MAX_MEDIA) { - skip(dis, contentLen); - out.println(new JSONObject().put("status","error").put("message","file too large/invalid").toString()); out.flush(); return; - } - - UUID messageId = UUID.fromString(h.getString("message_id")); - UUID senderId = UUID.fromString(h.getString("sender_id")); - String rType = h.getString("receiver_type"); // private/group/channel - UUID receiverId = UUID.fromString(h.getString("receiver_id")); - String messageType = h.getString("message_type").toUpperCase(); // IMAGE | AUDIO - - if (!"IMAGE".equals(messageType) && !"AUDIO".equals(messageType)) { - skip(dis, contentLen); - out.println(new JSONObject().put("status","error").put("message","unsupported message_type").toString()); out.flush(); return; - } - - String fileName = h.optString("file_name", "file.bin"); - String mimeType = h.optString("mime_type", "application/octet-stream"); - String text = h.optString("text", ""); // کپشن اختیاری - - Integer width = h.has("width") && !h.isNull("width") ? h.getInt("width") : null; - Integer height = h.has("height") && !h.isNull("height") ? h.getInt("height") : null; - - if (fileName.length() > 200) fileName = fileName.substring(0, 200); - - java.nio.file.Path baseDir = java.nio.file.Paths.get("uploads").toAbsolutePath().normalize(); - java.nio.file.Files.createDirectories(baseDir); - String kind = "IMAGE".equals(messageType) ? "images" : "audios"; - String subdir = kind + "/" + java.time.LocalDate.now(); - java.nio.file.Path dir = baseDir.resolve(subdir).normalize(); - java.nio.file.Files.createDirectories(dir); - - String ext = guessExt(fileName, mimeType); - String storedName = java.util.UUID.randomUUID() + ext; - java.nio.file.Path target = dir.resolve(storedName).normalize(); - - try (OutputStream fos = new BufferedOutputStream(java.nio.file.Files.newOutputStream( - target, java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING))) { - long remaining = contentLen; - byte[] buf = new byte[8192]; - while (remaining > 0) { - int toRead = (int) Math.min(buf.length, remaining); - int n = dis.read(buf, 0, toRead); - if (n == -1) throw new EOFException("stream ended early"); - fos.write(buf, 0, n); - remaining -= n; - } - } - - long fileSize = java.nio.file.Files.size(target); - - String storagePath = target.toString(); - String fileUrl = "/" + subdir.replace('\\','/') + "/" + storedName; - String mt = messageType; // "IMAGE" یا "AUDIO" - int safeWidth = ("IMAGE".equals(mt) && width != null) ? width : 0; - int safeHeight = ("IMAGE".equals(mt) && height != null) ? height : 0; - FileAttachment att = new FileAttachment(); - att.setFileUrl(fileUrl); - att.setFileType(messageType); // IMAGE/AUDIO - att.setFileName(fileName); - att.setFileSize(fileSize); - att.setMimeType(mimeType); - att.setWidth(safeWidth); - att.setHeight(safeHeight); - att.setDurationSeconds(0); - att.setThumbnailUrl(null); - att.setStoragePath(storagePath); - java.util.List atts = java.util.List.of(att); - - boolean ok = MessageDatabase.saveMessageWithOptionalAttachments( - messageId, senderId, receiverId, rType, text, messageType, atts - ); - - UUID mediaKey = null; - try (PreparedStatement q = ConnectionDb.connect().prepareStatement( - "SELECT media_key FROM message_attachments WHERE message_id = ? AND storage_path = ? LIMIT 1" - )) { - q.setObject(1, messageId); - q.setString(2, storagePath); - try (ResultSet rs = q.executeQuery()) { - if (rs.next()) mediaKey = (UUID) rs.getObject(1); - } - } catch (SQLException sqle) { - sqle.printStackTrace(); - } - - JSONObject ack = new JSONObject() - .put("status", ok ? "success" : "error") - .put("message_id", messageId.toString()) - .put("media_key", mediaKey != null ? mediaKey.toString() : JSONObject.NULL) - .put("file_name", fileName) - .put("file_size", fileSize) - .put("mime_type", mimeType) - .put("display_path", fileUrl); - - out.println(ack.toString()); - out.flush(); - - - // بعد از out.flush(); و فقط اگر ok==true - if (ok) { - try { - // 1) دریافت پیام از DB تا send_at و... دقیق باشد - Message m = MessageDatabase.findById(messageId); // اگر چنین متدی نداری، با پارامترهای همین متد بساز/پر کن - - // 2) لیست دریافت‌کنندگان بر اساس نوع چت - List receivers = getReceiversForChat(receiverId, rType.toLowerCase()); - - - // 3) ساخت payload شامل اتچمنت (media) - User sender = userDatabase.findByInternalUUID(senderId); - JSONObject payload = new JSONObject() - .put("action", "new_message") - .put("data", new JSONObject() - .put("id", m.getMessage_id().toString()) - .put("chat_id", receiverId.toString()) - .put("chat_type", rType.toLowerCase()) - .put("sender_id", senderId.toString()) - .put("sender_name", sender != null ? sender.getProfile_name() : JSONObject.NULL) - .put("message_type", messageType.toLowerCase()) - .put("text", (text == null || text.isEmpty()) ? JSONObject.NULL : text) - .put("media", new JSONObject() - .put("media_id", mediaKey != null ? mediaKey.toString() : JSONObject.NULL) - .put("file_name", fileName) - .put("mime_type", mimeType) - .put("size_bytes", fileSize) - .put("url", fileUrl) - .put("thumbnail_url", JSONObject.NULL) - .put("width", safeWidth) - .put("height", safeHeight) - .put("duration_ms", 0) - ) - .put("send_at", m.getSend_at().toString()) - .put("status", "SENT") - ); - - // 4) ارسال به همه اعضا (از جمله خودِ فرستنده اگر می‌خواهی UI آن هم یکپارچه آپدیت شود) - for (UUID uid : receivers) { - RealTimeEventDispatcher.sendToUser(uid, payload); - } - - // (اختیاری) رویداد آپدیت چت‌لیست برای sort بر اساس آخرین پیام - RealTimeEventDispatcher.notifyChatUpdated(receiverId, rType, m); - - } catch (Exception ex) { - ex.printStackTrace(); - // اگر ذخیره شد ولی Broadcast شکست خورد، می‌توانی Log کنی یا Retry سبک انجام دهی - } - } - - - } catch (Exception e) { - e.printStackTrace(); - out.println(new JSONObject().put("status","error").put("message","exception").toString()); - out.flush(); - } - } - - private static final int MAGIC_DL = 0x4D444D32; // "MDM2" - -// private void handleMediaDownload(DataInputStream inBin, DataOutputStream outBin, UUID requesterId) { -// try { -// int magic = inBin.readInt(); -// if (magic != MAGIC_DL) { sendDlErr(outBin, "bad magic"); return; } -// -// int hlen = inBin.readInt(); -// if (hlen <= 0 || hlen > 64 * 1024) { sendDlErr(outBin, "bad header length"); return; } -// -// byte[] hb = inBin.readNBytes(hlen); -// if (hb.length != hlen) { sendDlErr(outBin, "header truncated"); return; } -// -// JSONObject hdr = new JSONObject(new String(hb, java.nio.charset.StandardCharsets.UTF_8)); -// if (!"download".equalsIgnoreCase(hdr.optString("op"))) { sendDlErr(outBin, "bad op"); return; } -// -// UUID mediaKey = UUID.fromString(hdr.getString("media_key")); -// long offset = Math.max(0L, hdr.optLong("offset", 0L)); -// -// MediaRow mr = MessageDatabase.findMediaByKey(mediaKey); -// if (mr == null) { sendDlErr(outBin, "not found"); return; } -// if (!MessageDatabase.canAccess(requesterId, mr)) { sendDlErr(outBin, "not authorized"); return; } -// -// java.nio.file.Path path = java.nio.file.Paths.get(mr.storagePath).normalize(); -// long size = java.nio.file.Files.size(path); -// if (offset > size) offset = 0L; -// -// JSONObject ok = new JSONObject() -// .put("status","success") -// .put("media_key", mediaKey.toString()) -// .put("file_name", mr.fileName) -// .put("mime_type", mr.mimeType) -// .put("file_size", size); -// -// byte[] okb = ok.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); -// -// outBin.writeInt(MAGIC_DL); -// outBin.writeInt(okb.length); -// outBin.write(okb); -// outBin.writeLong(size - offset); -// -// try (java.io.InputStream fis = new java.io.BufferedInputStream(java.nio.file.Files.newInputStream(path))) { -// if (offset > 0) fis.skipNBytes(offset); -// byte[] buf = new byte[8192]; -// long remain = size - offset; -// while (remain > 0) { -// int n = fis.read(buf, 0, (int) Math.min(buf.length, remain)); -// if (n == -1) break; -// outBin.write(buf, 0, n); -// remain -= n; -// } -// } -// outBin.flush(); -// -// } catch (Exception e) { -// e.printStackTrace(); -// try { sendDlErr(outBin, "exception"); } catch (Exception ignored) {} -// } -// } - - private void handleMediaDownload(DataInputStream inBin, DataOutputStream outBin, UUID requesterId) { - try { - logf("MEDIA_DL start. requester=%s", requesterId); - - int magic = inBin.readInt(); - if (magic != MAGIC_DL) { sendDlErr(outBin, "bad magic"); return; } - - int hlen = inBin.readInt(); - if (hlen <= 0 || hlen > 64 * 1024) { sendDlErr(outBin, "bad header length"); return; } - - byte[] hb = inBin.readNBytes(hlen); - if (hb.length != hlen) { sendDlErr(outBin, "header truncated"); return; } - - String hdrStr = new String(hb, java.nio.charset.StandardCharsets.UTF_8); - logf("MEDIA_DL header: %s", hdrStr); - - JSONObject hdr = new JSONObject(hdrStr); - if (!"download".equalsIgnoreCase(hdr.optString("op"))) { sendDlErr(outBin, "bad op"); return; } - - UUID mediaKey = UUID.fromString(hdr.getString("media_key")); - long offset = Math.max(0L, hdr.optLong("offset", 0L)); - logf("Parsed mediaKey=%s offset=%d", mediaKey, offset); - - MediaRow mr = MessageDatabase.findMediaByKey(mediaKey); - if (mr == null) { sendDlErr(outBin, "not found"); return; } - - logf("MediaRow: chatType=%s chatId=%s sender=%s receiver=%s storage=%s", - mr.chatType, mr.chatId, mr.senderId, mr.receiverId, mr.storagePath); - - try (java.sql.Connection c = ConnectionDb.connect(); - java.sql.PreparedStatement st = c.prepareStatement( - "SELECT 1 FROM channel_subscribers WHERE channel_id = ? AND user_id = ? LIMIT 1")) { - st.setObject(1, mr.chatId, java.sql.Types.OTHER); - st.setObject(2, requesterId, java.sql.Types.OTHER); - boolean direct; - try (java.sql.ResultSet r = st.executeQuery()) { direct = r.next(); } - logf("[DL] direct channel membership ch=%s user=%s => %s", mr.chatId, requesterId, direct); - } catch (Exception e) { - logf("[DL] direct membership check ERROR: %s", e.toString()); - } - - boolean allowed = MessageDatabase.canAccess(requesterId, mr); - logf("canAccess(..) -> %s", allowed); - if (!allowed) { sendDlErr(outBin, "not authorized"); return; } - - java.nio.file.Path path = java.nio.file.Paths.get(mr.storagePath).normalize(); - long size = java.nio.file.Files.size(path); - if (offset > size) offset = 0L; - - JSONObject ok = new JSONObject() - .put("status","success") - .put("media_key", mediaKey.toString()) - .put("file_name", mr.fileName) - .put("mime_type", mr.mimeType) - .put("file_size", size); - - byte[] okb = ok.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); - - outBin.writeInt(MAGIC_DL); - outBin.writeInt(okb.length); - outBin.write(okb); - outBin.writeLong(size - offset); - logf("Sending OK header. file=%s size=%d offset=%d", mr.fileName, size, offset); - - try (java.io.InputStream fis = new java.io.BufferedInputStream(java.nio.file.Files.newInputStream(path))) { - if (offset > 0) fis.skipNBytes(offset); - byte[] buf = new byte[8192]; - long remain = size - offset; - while (remain > 0) { - int n = fis.read(buf, 0, (int) Math.min(buf.length, remain)); - if (n == -1) break; - outBin.write(buf, 0, n); - remain -= n; - } - } - outBin.flush(); - log("MEDIA_DL done."); - - } catch (Exception e) { - e.printStackTrace(); - try { sendDlErr(outBin, "exception"); } catch (Exception ignored) {} - } - } - - private void sendDlErr(DataOutputStream outBin, String msg) throws java.io.IOException { - JSONObject j = new JSONObject().put("status","error").put("message", msg); - byte[] b = j.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8); - outBin.writeInt(MAGIC_DL); - outBin.writeInt(b.length); - outBin.write(b); - outBin.writeLong(0L); - outBin.flush(); - } - - private static void skip(DataInputStream dis, long n) throws IOException { - if (n <= 0) return; - byte[] buf = new byte[8192]; - long left = n; - while (left > 0) { - int toRead = (int) Math.min(buf.length, left); - int r = dis.read(buf, 0, toRead); - if (r == -1) break; // EOF - left -= r; - } - } - - private static String guessExt(String original, String mime) { - if (original != null && original.contains(".")) { - String ext = original.substring(original.lastIndexOf('.')); - if (ext.length() <= 10) return ext.toLowerCase(); - } - if (mime == null) return ""; - - String m = mime.toLowerCase(); - - if (m.equals("image/png")) return ".png"; - if (m.equals("image/jpeg") || m.equals("image/jpg")) return ".jpg"; - if (m.equals("image/gif")) return ".gif"; - if (m.equals("image/webp")) return ".webp"; - - if (m.equals("audio/mpeg") || m.equals("audio/mp3")) return ".mp3"; - if (m.equals("audio/ogg")) return ".ogg"; - if (m.equals("audio/opus")) return ".opus"; - if (m.equals("audio/wav") || m.equals("audio/x-wav")) return ".wav"; - if (m.equals("audio/m4a") || m.equals("audio/mp4")) return ".m4a"; - -// if (m.equals("video/mp4")) return ".mp4"; -// if (m.equals("video/webm")) return ".webm"; - - // fallback - if (m.startsWith("image/")) return ""; - if (m.startsWith("audio/")) return ""; - if (m.startsWith("video/")) return ""; - - return ""; - } - - - - - - -// private ResponseModel handleSendMessage(JSONObject json) { -// -// try { -// if (currentUser == null) -// return new ResponseModel("error", "Unauthorized. Please login first."); -// -// UUID messageId = UUID.randomUUID(); -// UUID senderId = currentUser.getInternal_uuid(); -// String receiverType = json.getString("receiver_type"); -// UUID receiverId; -// receiverId = UUID.fromString(json.getString("receiver_id")); -// -// if(Objects.equals(receiverType, "private")){ -// PrivateChatDatabase.clearDeletedFlag(senderId, receiverId); -// UUID other = PrivateChatDatabase.getOtherParticipant(receiverId, senderId); -// if (other == null) { -// return new ResponseModel("error", "Invalid private chat."); -// } -// if (ContactDatabase.isBlocked(senderId, other) || ContactDatabase.isBlocked(other, senderId)) { -// return new ResponseModel("error", "You can't message this user (blocked)."); -// } -// } -// -// -// String content = json.optString("content", ""); -// String messageType = json.optString("message_type", "TEXT"); -// -// boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType); -// if (!inserted) -// return new ResponseModel("error", "Failed to insert message."); -// -// if (json.has("attachments")) { -// JSONArray attachmentsArray = json.getJSONArray("attachments"); -// List attachments = new ArrayList<>(); -// -// for (int i = 0; i < attachmentsArray.length(); i++) { -// JSONObject attJson = attachmentsArray.getJSONObject(i); -// attachments.add(new FileAttachment( -// attJson.getString("file_url"), -// attJson.getString("file_type") -// )); -// } -// -// boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments); -// if (!attInserted) -// return new ResponseModel("error", "Message inserted but failed to attach files."); -// } -// -// // Send real-time message -// Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now()); -// List receivers = getReceiversForChat(receiverId, receiverType); -// receivers.remove(senderId); -// RealTimeEventDispatcher.sendNewMessage(msg, receivers); -// -// // Update chat list (last_message_time) -// JSONObject chatUpdate = new JSONObject(); -// chatUpdate.put("chat_id", receiverId.toString()); -// chatUpdate.put("chat_type", receiverType); -// chatUpdate.put("last_message_time", LocalDateTime.now().toString()); -// -// JSONObject chatPayload = new JSONObject(); -// chatPayload.put("action", "chat_updated"); -// chatPayload.put("data", chatUpdate); -// -// for (UUID receiver : receivers) { -// RealTimeEventDispatcher.sendToUser(receiver, chatPayload); -// } -// -// JSONObject data = new JSONObject(); -// data.put("message_id", messageId.toString()); -// return new ResponseModel("success", "Message sent successfully.", data); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return new ResponseModel("error", "Exception occurred while sending message."); -// } -// } - - private ResponseModel handleSendMessage(JSONObject json) { + try { if (currentUser == null) return new ResponseModel("error", "Unauthorized. Please login first."); @@ -3593,71 +2905,57 @@ public class ClientHandler implements Runnable { UUID messageId = UUID.randomUUID(); UUID senderId = currentUser.getInternal_uuid(); String receiverType = json.getString("receiver_type"); - UUID receiverId = UUID.fromString(json.getString("receiver_id")); + UUID receiverId; + receiverId = UUID.fromString(json.getString("receiver_id")); + if(Objects.equals(receiverType, "private")){ + PrivateChatDatabase.clearDeletedFlag(senderId, receiverId); + UUID other = PrivateChatDatabase.getOtherParticipant(receiverId, senderId); + if (other == null) { + return new ResponseModel("error", "Invalid private chat."); + } + if (ContactDatabase.isBlocked(senderId, other) || ContactDatabase.isBlocked(other, senderId)) { + return new ResponseModel("error", "You can't message this user (blocked)."); + } + } String content = json.optString("content", ""); String messageType = json.optString("message_type", "TEXT"); - // Parse attachments - List attachments = new ArrayList<>(); + boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType); + if (!inserted) + return new ResponseModel("error", "Failed to insert message."); + if (json.has("attachments")) { - JSONArray arr = json.getJSONArray("attachments"); - for (int i = 0; i < arr.length(); i++) { - JSONObject a = arr.getJSONObject(i); + JSONArray attachmentsArray = json.getJSONArray("attachments"); + List attachments = new ArrayList<>(); + + for (int i = 0; i < attachmentsArray.length(); i++) { + JSONObject attJson = attachmentsArray.getJSONObject(i); attachments.add(new FileAttachment( - a.optString("file_url",""), - a.optString("file_type","FILE"), - a.optString("file_name",""), - a.has("file_size") && !a.isNull("file_size") ? a.getLong("file_size") : null, - a.optString("mime_type", null), - a.has("width") && !a.isNull("width") ? a.getInt("width") : null, - a.has("height") && !a.isNull("height") ? a.getInt("height") : null, - a.has("duration_seconds") && !a.isNull("duration_seconds") ? a.getInt("duration_seconds") : null, - a.isNull("thumbnail_url") ? null : a.optString("thumbnail_url", null) + attJson.getString("file_url"), + attJson.getString("file_type") )); } + + boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments); + if (!attInserted) + return new ResponseModel("error", "Message inserted but failed to attach files."); } - if ((content == null || content.isBlank()) && attachments.isEmpty()) { - return new ResponseModel("error", "Empty message: no content or attachment."); - } - - // Harmonize message_type - if (!attachments.isEmpty()) { - String firstType = attachments.get(0).getFileType(); - if ("TEXT".equalsIgnoreCase(messageType)) { - messageType = firstType; - } else if (!messageType.equalsIgnoreCase(firstType) && !messageType.equalsIgnoreCase("FILE")) { - return new ResponseModel("error", "message_type and attachment.file_type mismatch."); - } - } - - // DB transaction - try (Connection conn = ConnectionDb.connect()) { - conn.setAutoCommit(false); - - boolean inserted = MessageDatabase.insertMessageTx(conn, messageId, senderId, receiverId, receiverType, content, messageType); - if (!inserted) { - conn.rollback(); - return new ResponseModel("error", "Failed to insert message."); - } - - if (!attachments.isEmpty()) { - boolean attInserted = MessageDatabase.insertAttachmentsTx(conn, messageId, attachments); - if (!attInserted) { - conn.rollback(); - return new ResponseModel("error", "Message inserted but failed to attach files."); - } - } - - conn.commit(); - } - - // Real-Time + // Send real-time message Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now()); + List receivers = getReceiversForChat(receiverId, receiverType); + receivers.remove(senderId); + RealTimeEventDispatcher.sendNewMessage(msg, receivers); + + // Update chat list (last_message_time) + JSONObject chatUpdate = new JSONObject(); + chatUpdate.put("chat_id", receiverId.toString()); + chatUpdate.put("chat_type", receiverType); + chatUpdate.put("last_message_time", LocalDateTime.now().toString()); JSONObject chatPayload = new JSONObject(); chatPayload.put("action", "chat_updated"); @@ -3669,76 +2967,9 @@ public class ClientHandler implements Runnable { RealTimeEventDispatcher.sendToUser(senderId, chatPayload); - JSONObject payload = new JSONObject(); - payload.put("action", "new_message"); JSONObject data = new JSONObject(); - data.put("id", messageId.toString()); - data.put("sender_id", senderId.toString()); - data.put("receiver_id", receiverId.toString()); - data.put("receiver_type", receiverType); - data.put("content", content); - data.put("message_type", messageType); - data.put("send_at", msg.getSend_at().toString()); - - if (!attachments.isEmpty()) { - JSONArray out = new JSONArray(); - for (FileAttachment a : attachments) { - JSONObject ao = new JSONObject() - .put("file_url", a.getFileUrl()) - .put("file_type", a.getFileType()) - .put("file_name", a.getFileName() == null ? JSONObject.NULL : a.getFileName()) - .put("file_size", a.getFileSize() == null ? JSONObject.NULL : a.getFileSize()) - .put("mime_type", a.getMimeType() == null ? JSONObject.NULL : a.getMimeType()) - .put("width", a.getWidth() == null ? JSONObject.NULL : a.getWidth()) - .put("height", a.getHeight() == null ? JSONObject.NULL : a.getHeight()) - .put("duration_seconds", a.getDurationSeconds() == null ? JSONObject.NULL : a.getDurationSeconds()) - .put("thumbnail_url", a.getThumbnailUrl() == null ? JSONObject.NULL : a.getThumbnailUrl()); - out.put(ao); - } - data.put("attachments", out); - } - - User sender = userDatabase.findByInternalUUID(senderId); - if (sender != null) data.put("sender_name", sender.getProfile_name()); - payload.put("data", data); - -// List receivers = getReceiversForChat(receiverId, receiverType); -// receivers.remove(senderId); -// RealTimeEventDispatcher.broadcastToUsers(receivers, payload); -// -// -// -// // chat_updated -// JSONObject chatUpdate = new JSONObject() -// .put("chat_id", receiverId.toString()) -// .put("chat_type", receiverType) -// .put("last_message_time", LocalDateTime.now().toString()); -// -// JSONObject chatPayload = new JSONObject() -// .put("action", "chat_updated") -// .put("data", chatUpdate); -// -// for (UUID r : receivers) RealTimeEventDispatcher.sendToUser(r, chatPayload); - - - List allMembers = getReceiversForChat(receiverId, receiverType); // شامل sender - // به همه chat_updated بده - JSONObject chatUpdate = new JSONObject() - .put("chat_id", receiverId.toString()) - .put("chat_type", receiverType) - .put("last_message_time", LocalDateTime.now().toString()); - JSONObject chatPayload = new JSONObject() - .put("action", "chat_updated") - .put("data", chatUpdate); - for (UUID u : allMembers) RealTimeEventDispatcher.sendToUser(u, chatPayload); - - List others = new ArrayList<>(allMembers); - others.remove(senderId); - RealTimeEventDispatcher.broadcastToUsers(others, payload); - - - JSONObject respData = new JSONObject().put("message_id", messageId.toString()); - return new ResponseModel("success", "Message sent successfully.", respData); + data.put("message_id", messageId.toString()); + return new ResponseModel("success", "Message sent successfully.", data); } catch (Exception e) { e.printStackTrace(); @@ -3747,7 +2978,6 @@ public class ClientHandler implements Runnable { } - private List getReceiversForChat(UUID receiverId, String receiverType) { switch (receiverType) { case "private": diff --git a/src/main/java/org/to/telegramfinalproject/UI/LoginController.java b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java index c3a5124..2e16767 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/LoginController.java +++ b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java @@ -40,7 +40,7 @@ public class LoginController { visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty()); try { - connection = new ClientConnection("localhost", 8000); + connection = new ClientConnection("localhost", 8080); } catch (Exception e) { System.out.println("Could not connect to server: " + e.getMessage()); }