Merge remote-tracking branch 'origin/develop' into Sidebar-Menu
# Conflicts: # src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java # src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java # src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java # src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java # src/main/java/org/to/telegramfinalproject/Models/Message.java # src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java # src/main/resources/init.sql
This commit is contained in:
@@ -344,6 +344,7 @@ public class ActionHandler {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void createGroup() {
|
||||
String groupId = null;
|
||||
|
||||
@@ -524,7 +525,7 @@ public class ActionHandler {
|
||||
chat.optBoolean("is_admin", false)
|
||||
);
|
||||
if (chat.has("other_user_id")) {
|
||||
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // 👈 اضافه کردن برای private chat
|
||||
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id")));
|
||||
}
|
||||
archivedChats.add(entry);
|
||||
|
||||
@@ -545,7 +546,7 @@ public class ActionHandler {
|
||||
chat.optBoolean("is_admin", false)
|
||||
);
|
||||
if (chat.has("other_user_id")) {
|
||||
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // 👈 اضافه کردن برای private chat
|
||||
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id")));
|
||||
}
|
||||
|
||||
activeChats.add(entry);
|
||||
@@ -615,12 +616,24 @@ public class ActionHandler {
|
||||
String type = item.getString("type");
|
||||
|
||||
if (type.equals("message")) {
|
||||
System.out.println((i + 1) + ". [message] \"" + item.getString("content") + "\""
|
||||
+ " (from: " + item.optString("sender", "N/A") + ", at: " + item.getString("time") + ")");
|
||||
String senderName = item.optString("sender_name", item.optString("sender", "Unknown"));
|
||||
String time = item.optString("time", "Unknown time");
|
||||
String content = item.optString("content", "[No content]");
|
||||
String context = "";
|
||||
|
||||
if (item.has("group_name")) {
|
||||
context = " in group: " + item.getString("group_name");
|
||||
} else if (item.has("channel_name")) {
|
||||
context = " in channel: " + item.getString("channel_name");
|
||||
}
|
||||
|
||||
System.out.println((i + 1) + ". [message] \"" + content + "\""
|
||||
+ " (from: " + senderName + context + ", at: " + time + ")");
|
||||
} else {
|
||||
System.out.println((i + 1) + ". [" + type + "] "
|
||||
+ item.getString("name") + " (ID: " + item.getString("id") + ")");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.out.print("Select a result number to interact (or 0 to exit): ");
|
||||
@@ -722,6 +735,22 @@ public class ActionHandler {
|
||||
case "message" -> {
|
||||
String receiverId = selected.getString("receiver_id");
|
||||
String receiverType = selected.getString("receiver_type");
|
||||
String senderName = selected.optString("sender_name", "Unknown");
|
||||
String content = selected.optString("content", "[No content]");
|
||||
String time = selected.optString("time", "Unknown time");
|
||||
|
||||
String context = "";
|
||||
if (receiverType.equals("group")) {
|
||||
context = "in group: " + selected.optString("group_name", "Unknown group");
|
||||
} else if (receiverType.equals("channel")) {
|
||||
context = "in channel: " + selected.optString("channel_name", "Unknown channel");
|
||||
}
|
||||
|
||||
System.out.println("📩 Message preview:");
|
||||
System.out.println("From: " + senderName);
|
||||
if (!context.isEmpty()) System.out.println(context);
|
||||
System.out.println("Time: " + time);
|
||||
System.out.println("Content: \"" + content + "\"");
|
||||
|
||||
System.out.println("🔍 Looking for chat with displayId='" + receiverId + "' type='" + receiverType + "'");
|
||||
|
||||
@@ -733,6 +762,7 @@ public class ActionHandler {
|
||||
openChat(chat);
|
||||
}
|
||||
|
||||
|
||||
default -> System.out.println("No interaction available for type: " + type);
|
||||
}
|
||||
}
|
||||
@@ -1392,7 +1422,8 @@ public class ActionHandler {
|
||||
System.out.println("4. Delete chat (both sides)");
|
||||
System.out.println("5. View profile");
|
||||
System.out.println("6. Archive/Unarchived");
|
||||
System.out.println("7. Back");
|
||||
System.out.println("7. View messages");
|
||||
System.out.println("8. Back");
|
||||
|
||||
|
||||
String input = scanner.nextLine();
|
||||
@@ -1438,8 +1469,11 @@ public class ActionHandler {
|
||||
|
||||
return true;
|
||||
}
|
||||
case "7" ->{
|
||||
viewMessagesInChat(chat);
|
||||
}
|
||||
|
||||
case "7" -> {
|
||||
case "8" -> {
|
||||
return false;
|
||||
}
|
||||
default -> System.out.println("Invalid choice.");
|
||||
@@ -1505,6 +1539,7 @@ public class ActionHandler {
|
||||
}
|
||||
|
||||
System.out.println("12. Archive/Unarchived");
|
||||
System.out.println("13. View messages");
|
||||
|
||||
System.out.println("0. Back to Chat List");
|
||||
|
||||
@@ -1559,6 +1594,10 @@ public class ActionHandler {
|
||||
toggleArchive(chat.getId() , "group");
|
||||
}
|
||||
|
||||
case "13" ->{
|
||||
viewMessagesInChat(chat);
|
||||
}
|
||||
|
||||
case "0" -> {
|
||||
return false;
|
||||
}
|
||||
@@ -1626,6 +1665,7 @@ public class ActionHandler {
|
||||
}
|
||||
|
||||
System.out.println("12. Archive/Unarchived");
|
||||
System.out.println("13. View messages");
|
||||
System.out.println("0. Back to Chat List");
|
||||
|
||||
String input = scanner.nextLine();
|
||||
@@ -1708,7 +1748,9 @@ public class ActionHandler {
|
||||
case "12"->{
|
||||
toggleArchive(chat.getId() , "channel");
|
||||
}
|
||||
|
||||
case "13" ->{
|
||||
viewMessagesInChat(chat);
|
||||
}
|
||||
|
||||
case "0" -> {
|
||||
return false;
|
||||
@@ -3169,7 +3211,7 @@ public class ActionHandler {
|
||||
}
|
||||
case "2" -> {
|
||||
addContact(chat.getId());
|
||||
refreshChatList();
|
||||
refreshContactList();
|
||||
}
|
||||
default -> System.out.println("Back...");
|
||||
}
|
||||
@@ -3446,6 +3488,397 @@ public class ActionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshContactList() {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "get_contact_list");
|
||||
req.put("user_id", Session.currentUser.getString("user_id"));
|
||||
out.println(req.toString());
|
||||
|
||||
try {
|
||||
JSONObject response = TelegramClient.responseQueue.take();
|
||||
|
||||
if (response != null && response.getString("status").equals("success")) {
|
||||
if (response.has("data") && !response.isNull("data")) {
|
||||
JSONObject data = response.getJSONObject("data");
|
||||
|
||||
if (!data.has("contact_list") || data.isNull("contact_list")) {
|
||||
System.out.println("❌ contact_list not found in response data.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONArray contactListJson = data.getJSONArray("contact_list");
|
||||
List<ContactEntry> contactList = new ArrayList<>();
|
||||
|
||||
for (Object obj : contactListJson) {
|
||||
JSONObject c = (JSONObject) obj;
|
||||
|
||||
ContactEntry entry = new ContactEntry(
|
||||
UUID.fromString(c.getString("contact_id")),
|
||||
c.getString("user_id"),
|
||||
c.getString("profile_name"),
|
||||
c.optString("image_url", ""),
|
||||
c.optBoolean("is_blocked", false)
|
||||
);
|
||||
|
||||
contactList.add(entry);
|
||||
}
|
||||
|
||||
Session.contactEntries = contactList;
|
||||
System.out.println("✅ Contact list updated. Total: " + contactList.size());
|
||||
|
||||
} else {
|
||||
System.out.println("⚠️ Response has no data object.");
|
||||
}
|
||||
} else {
|
||||
if (response.has("message") && !response.isNull("message")) {
|
||||
System.out.println("❌ Failed to refresh contact list: " + response.getString("message"));
|
||||
} else {
|
||||
System.out.println("❌ Failed to refresh contact list.");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("❌ Error during refreshContactList: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void viewMessagesInChat(ChatEntry chat) {
|
||||
int offset = 0;
|
||||
int limit = 10;
|
||||
|
||||
while (true) {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "get_chat_messages");
|
||||
req.put("chat_id", chat.getId());
|
||||
req.put("chat_type", chat.getType());
|
||||
req.put("offset", offset);
|
||||
req.put("limit", limit);
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res == null || !res.getString("status").equals("success")) {
|
||||
System.out.println("❌ Failed to fetch messages.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONArray messages = res.getJSONObject("data").getJSONArray("get_chat_messages");
|
||||
|
||||
if (messages.isEmpty()) {
|
||||
if (offset == 0)
|
||||
System.out.println("📭 No messages in this chat.");
|
||||
else
|
||||
System.out.println("📭 No more messages.");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("\n📥 Messages:");
|
||||
System.out.println("─────────────────────────────────────────────");
|
||||
for (int i = 0; i < messages.length(); i++) {
|
||||
JSONObject msg = messages.getJSONObject(i);
|
||||
String senderName = msg.optString("sender_name", "Unknown");
|
||||
String content = msg.optString("content", "(no content)");
|
||||
String time = msg.optString("time", "");
|
||||
boolean isEdited = msg.optBoolean("is_edited", false);
|
||||
String editedAt = msg.optString("edited_at", "");
|
||||
String label = isEdited ? "🖊️ (edited at: " + editedAt + ")" : "";
|
||||
|
||||
// ✅ Forwarded info
|
||||
String forwardLabel = "";
|
||||
if (msg.optBoolean("is_forwarded", false)) {
|
||||
String forwardFrom = msg.optString("forwarded_from_name", "Unknown");
|
||||
forwardLabel = "🔁 Forwarded from " + forwardFrom;
|
||||
}
|
||||
|
||||
//for reply
|
||||
String replyLabel = "";
|
||||
if (msg.has("reply_to_id")) {
|
||||
String repliedSender = msg.optString("reply_to_sender", "Unknown");
|
||||
String repliedContent = msg.optString("reply_to_content", "...");
|
||||
replyLabel = "↪️ Reply to " + repliedSender + ": \"" + repliedContent + "\"";
|
||||
}
|
||||
|
||||
JSONArray reactions = msg.optJSONArray("reactions");
|
||||
if (reactions != null && !reactions.isEmpty()) {
|
||||
System.out.print(" 💬 Reactions: ");
|
||||
for (int j = 0; j < reactions.length(); j++) {
|
||||
System.out.print(reactions.getString(j) + " ");
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
System.out.printf("[%d] [%s] %s: \n", i + 1, time, senderName);
|
||||
if (!forwardLabel.isEmpty()) System.out.println(forwardLabel);
|
||||
if (!replyLabel.isEmpty()) System.out.println(replyLabel);
|
||||
System.out.printf("%s %s\n", content, label);
|
||||
System.out.println("─────────────────────────────────────────────");
|
||||
}
|
||||
|
||||
System.out.print("""
|
||||
💬 Options:
|
||||
[number] - Interact with message
|
||||
N - Next page (older messages)
|
||||
0 - Back to chat menu
|
||||
S - Send message
|
||||
➤ Choice: """);
|
||||
|
||||
String input = scanner.nextLine().trim();
|
||||
|
||||
if (input.equals("0")) return;
|
||||
if (input.equalsIgnoreCase("N")) {
|
||||
offset += limit;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(input.equalsIgnoreCase("S")){
|
||||
sendMessage(chat.getId(), chat.getType());
|
||||
}
|
||||
try {
|
||||
int index = Integer.parseInt(input);
|
||||
if (index < 1 || index > messages.length()) {
|
||||
System.out.println("❌ Invalid message number.");
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject selected = messages.getJSONObject(index - 1);
|
||||
UUID messageId = UUID.fromString(selected.getString("message_id"));
|
||||
UUID senderId = UUID.fromString(selected.getString("sender_id"));
|
||||
boolean isSender = senderId.toString().equals(Session.currentUser.getString("internal_uuid"));
|
||||
boolean isChannel = chat.getType().equals("channel");
|
||||
boolean isOwnerOrAdmin = chat.isOwner() || chat.isAdmin();
|
||||
|
||||
System.out.println("\n🎯 Selected message by " + selected.getString("sender_name"));
|
||||
|
||||
// ✔ Show interaction menu based on access
|
||||
System.out.println("2. Forward");
|
||||
|
||||
if (isChannel) {
|
||||
if (isOwnerOrAdmin) {
|
||||
System.out.println("1. Reply");
|
||||
System.out.println("3. React");
|
||||
if (isSender) {
|
||||
System.out.println("4. Edit");
|
||||
}
|
||||
System.out.println("5. Delete");
|
||||
}
|
||||
} else {
|
||||
System.out.println("1. Reply");
|
||||
System.out.println("3. React");
|
||||
if (isSender) {
|
||||
System.out.println("4. Edit");
|
||||
}
|
||||
if (isSender || isOwnerOrAdmin) {
|
||||
System.out.println("5. Delete");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("0. Back to message list");
|
||||
System.out.print("➤ Select an action: ");
|
||||
String choice = scanner.nextLine().trim();
|
||||
|
||||
switch (choice) {
|
||||
case "1" -> {
|
||||
if (!isChannel || isOwnerOrAdmin)
|
||||
replyToMessage(messageId);
|
||||
else
|
||||
System.out.println("❌ You are not allowed to reply.");
|
||||
}
|
||||
case "2" -> forwardMessage(messageId);
|
||||
case "3" -> {
|
||||
if (!isChannel || isOwnerOrAdmin)
|
||||
reactToMessage(messageId);
|
||||
else
|
||||
System.out.println("❌ You are not allowed to react.");
|
||||
}
|
||||
case "4" -> {
|
||||
if (isSender)
|
||||
editMessage(messageId);
|
||||
else
|
||||
System.out.println("❌ You can only edit your own messages.");
|
||||
}
|
||||
case "5" -> {
|
||||
if (!isChannel || isOwnerOrAdmin || isSender)
|
||||
deleteMessage(messageId);
|
||||
else
|
||||
System.out.println("❌ You are not allowed to delete this message.");
|
||||
}
|
||||
case "0" -> {}
|
||||
default -> System.out.println("❌ Invalid option.");
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("❌ Please enter a valid number or command.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void editMessage(UUID messageId) {
|
||||
System.out.print("📝 Enter new content: ");
|
||||
String newContent = scanner.nextLine().trim();
|
||||
|
||||
if (newContent.isEmpty()) {
|
||||
System.out.println("❌ Content cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "edit_message");
|
||||
req.put("message_id", messageId.toString());
|
||||
req.put("new_content", newContent);
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res == null || !res.getString("status").equals("success")) {
|
||||
System.out.println("❌ Failed to edit message.");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("✅ Message updated successfully.");
|
||||
}
|
||||
|
||||
|
||||
private void reactToMessage(UUID messageId) {
|
||||
System.out.print("😊 Enter your reaction (e.g., ❤️, 👍, 😂): ");
|
||||
String reaction = scanner.nextLine().trim();
|
||||
|
||||
if (reaction.isEmpty()) {
|
||||
System.out.println("⚠️ Empty reaction discarded.");
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "react_to_message");
|
||||
req.put("message_id", messageId.toString());
|
||||
req.put("reaction", reaction);
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.getString("status").equals("success")) {
|
||||
System.out.println("✅ Reaction sent.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to react to message.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void forwardMessage(UUID originalMessageId) {
|
||||
System.out.println("\n📤 Select a chat to forward this message to:");
|
||||
|
||||
List<ChatEntry> chatList = Session.chatList;
|
||||
for (int i = 0; i < chatList.size(); i++) {
|
||||
ChatEntry c = chatList.get(i);
|
||||
System.out.printf("%d. [%s] %s\n", i + 1, c.getType(), c.getName());
|
||||
}
|
||||
System.out.println("0. Cancel");
|
||||
|
||||
System.out.print("➤ Enter chat number: ");
|
||||
String input = scanner.nextLine().trim();
|
||||
|
||||
if (input.equals("0")) return;
|
||||
|
||||
try {
|
||||
int choice = Integer.parseInt(input);
|
||||
if (choice < 1 || choice > chatList.size()) {
|
||||
System.out.println("❌ Invalid choice.");
|
||||
return;
|
||||
}
|
||||
|
||||
ChatEntry target = chatList.get(choice - 1);
|
||||
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "forward_message");
|
||||
req.put("target_chat_id", target.getId());
|
||||
req.put("target_chat_type", target.getType());
|
||||
req.put("original_message_id", originalMessageId.toString());
|
||||
req.put("forwarded_by", Session.currentUser.getString("internal_uuid"));
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.getString("status").equals("success")) {
|
||||
System.out.println("✅ Message forwarded successfully.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to forward the message.");
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
System.out.println("❌ Invalid input.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void replyToMessage(UUID messageId) {
|
||||
System.out.print("💬 Enter your reply: ");
|
||||
String content = scanner.nextLine().trim();
|
||||
|
||||
if (content.isEmpty()) {
|
||||
System.out.println("⚠️ Empty reply discarded.");
|
||||
return;
|
||||
}
|
||||
|
||||
UUID chatId = UUID.fromString(Session.currentChatId);
|
||||
String chatType = Session.currentChatType;
|
||||
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "send_reply_message");
|
||||
req.put("receiver_type", chatType);
|
||||
req.put("receiver_id", chatId.toString());
|
||||
req.put("content", content);
|
||||
req.put("reply_to_id", messageId.toString());
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
|
||||
if (res != null && res.getString("status").equals("success")) {
|
||||
System.out.println("✅ Reply sent.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to send reply.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void deleteMessage(UUID messageId) {
|
||||
System.out.println("\n🗑️ Delete Message Options:");
|
||||
System.out.println("1. Delete for yourself (one-sided)");
|
||||
System.out.println("2. Delete for everyone (global) [only if allowed]");
|
||||
System.out.println("0. Cancel");
|
||||
|
||||
String choice = scanner.nextLine().trim();
|
||||
|
||||
switch (choice) {
|
||||
case "1" -> {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "delete_message");
|
||||
req.put("message_id", messageId.toString());
|
||||
req.put("delete_type", "one-sided");
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.getString("status").equals("success")) {
|
||||
System.out.println("✅ Message deleted for you.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to delete message.");
|
||||
}
|
||||
}
|
||||
|
||||
case "2" -> {
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "delete_message");
|
||||
req.put("message_id", messageId.toString());
|
||||
req.put("delete_type", "global");
|
||||
|
||||
JSONObject res = sendWithResponse(req);
|
||||
if (res != null && res.getString("status").equals("success")) {
|
||||
System.out.println("✅ Message deleted for everyone.");
|
||||
} else {
|
||||
System.out.println("❌ Failed to delete message globally.");
|
||||
if (res != null) System.out.println("⚠️ " + res.optString("message"));
|
||||
}
|
||||
}
|
||||
|
||||
case "0" -> {
|
||||
System.out.println("❎ Delete canceled.");
|
||||
}
|
||||
|
||||
default -> {
|
||||
System.out.println("❌ Invalid option.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showSidebarMenu() {
|
||||
System.out.println("\n--- Sidebar Menu ---");
|
||||
System.out.println("1. View Profile");
|
||||
@@ -3493,6 +3926,7 @@ public class ActionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -74,24 +74,24 @@ public class IncomingMessageListener implements Runnable {
|
||||
|
||||
private boolean isRealTimeEvent(String action) {
|
||||
return switch (action) {
|
||||
case "new_message", "message_edited", "message_deleted",
|
||||
case "new_message", "message_edited", "message_deleted_global",
|
||||
"user_status_changed", "added_to_group", "added_to_channel",
|
||||
"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" -> true;
|
||||
"became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated","created_private_chat" , "message_reacted" , "message_unreacted" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
void handleRealTimeEvent(JSONObject response) throws IOException {
|
||||
String action = response.getString("action");
|
||||
JSONObject msg = response.getJSONObject("data");
|
||||
JSONObject msg = response.has("data") ? response.getJSONObject("data") : new JSONObject();
|
||||
|
||||
|
||||
switch (action) {
|
||||
case "added_to_group", "added_to_channel",
|
||||
"removed_from_group", "removed_from_channel", "chat_deleted" -> {
|
||||
"removed_from_group", "removed_from_channel", "chat_deleted","created_private_chat" -> {
|
||||
System.out.println("🔄 Chat list changed. Updating...");
|
||||
Session.forceRefreshChatList = true;
|
||||
System.out.println("🧪 Calling requestChatList() after being added");
|
||||
@@ -135,10 +135,6 @@ public class IncomingMessageListener implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
|
||||
|
||||
|
||||
default -> displayRealTimeMessage(action, msg);
|
||||
}
|
||||
|
||||
@@ -298,36 +294,52 @@ public class IncomingMessageListener implements Runnable {
|
||||
private void displayRealTimeMessage(String action, JSONObject msg) {
|
||||
switch (action) {
|
||||
case "new_message" -> {
|
||||
System.out.println("\n🔔 New Message Received:");
|
||||
String senderName = msg.optString("sender_name", "Unknown");
|
||||
String content = msg.optString("content", "(empty)");
|
||||
String sendAt = msg.optString("send_at", "-");
|
||||
String senderName = msg.optString("sender_name","Unknown");
|
||||
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");
|
||||
JSONObject meta = msg.optJSONObject("meta");
|
||||
|
||||
String receiverId = msg.optString("receiver_id", "");
|
||||
String receiverType = msg.optString("receiver_type", "");
|
||||
String prefix = "";
|
||||
if ("reply".equals(kind) && meta != null) {
|
||||
var rt = meta.optJSONObject("reply_to");
|
||||
prefix = "[reply → " + (rt!=null?rt.optString("excerpt",""):"") + "] ";
|
||||
} else if ("forward".equals(kind) && meta != null) {
|
||||
var ff = meta.optJSONObject("forwarded_from");
|
||||
prefix = "[forwarded from " + (ff!=null?ff.optString("sender_name","unknown"):"unknown") + "] ";
|
||||
}
|
||||
|
||||
boolean isInCurrentChat = Session.inChatMenu &&
|
||||
Session.currentChatId != null &&
|
||||
Session.currentChatId.equals(receiverId);
|
||||
Session.currentChatId != null && Session.currentChatId.equals(chatId);
|
||||
|
||||
if (isInCurrentChat) {
|
||||
System.out.println(senderName + ": " + content + " (" + sendAt + ")");
|
||||
System.out.println(senderName + ": " + prefix + content + " (" + sendAt + ")");
|
||||
} else {
|
||||
System.out.println("💬 Message from " + senderName + " in " + receiverType + " chat: " + content);
|
||||
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.getString("message_id"));
|
||||
System.out.println("New Content: " + msg.getString("new_content"));
|
||||
System.out.println("Edit Time: " + msg.getString("edited_at"));
|
||||
}
|
||||
case "message_deleted" -> {
|
||||
case "message_deleted_global" -> {
|
||||
System.out.println("\n🗑️ Message Deleted:");
|
||||
System.out.println("Message ID: " + msg.getString("message_id"));
|
||||
}
|
||||
case "message_reacted", "message_unreacted" -> {
|
||||
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.getString("user_id"));
|
||||
|
||||
@@ -27,8 +27,7 @@ public class Session {
|
||||
public static String currentChatId = null;
|
||||
public static ChatEntry currentChatEntry = null;
|
||||
public static List<ContactEntry> contactEntries = new ArrayList<>();
|
||||
|
||||
|
||||
public static boolean inContactListMenu = false;
|
||||
|
||||
|
||||
public static String getUserUUID() {
|
||||
|
||||
@@ -82,7 +82,6 @@ public class ContactDatabase {
|
||||
}
|
||||
return !currentlyBlocked;
|
||||
} else {
|
||||
// اگر رابطه وجود نداره، اول باید کاربر رو به contact ها اضافه کنیم
|
||||
String insertSql = "INSERT INTO contacts (user_id, contact_id, is_blocked) VALUES (?, ?, ?)";
|
||||
try (PreparedStatement insertStmt = conn.prepareStatement(insertSql)) {
|
||||
insertStmt.setObject(1, userId);
|
||||
@@ -152,8 +151,13 @@ public class ContactDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean eitherBlocks(UUID userA, UUID userB) {
|
||||
return isBlocked(userA, userB) || isBlocked(userB, userA);
|
||||
}
|
||||
|
||||
public boolean isBlocked(UUID user_id, UUID contact_id) {
|
||||
|
||||
|
||||
public static boolean isBlocked(UUID user_id, UUID contact_id) {
|
||||
String sql = "SELECT is_blocked FROM contacts WHERE user_id = ? AND contact_id = ?";
|
||||
try (Connection connection = getConnection()) {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
@@ -241,6 +245,39 @@ public class ContactDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ContactEntry> getContactEntries(UUID userId) {
|
||||
List<ContactEntry> entries = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT c.contact_id, c.is_blocked, u.user_id, u.profile_name, u.image_url
|
||||
FROM contacts c
|
||||
JOIN users u ON c.contact_id = u.internal_uuid
|
||||
WHERE c.user_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, userId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
UUID contactId = UUID.fromString(rs.getString("contact_id"));
|
||||
boolean isBlocked = rs.getBoolean("is_blocked");
|
||||
String userIdStr = rs.getString("user_id");
|
||||
String profileName = rs.getString("profile_name");
|
||||
String imageUrl = rs.getString("image_url");
|
||||
|
||||
ContactEntry entry = new ContactEntry(contactId, userIdStr, profileName, imageUrl, isBlocked);
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static List<ContactEntry> searchContacts(UUID userId, String searchTerm) {
|
||||
List<ContactEntry> results = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.to.telegramfinalproject.Models.Message;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -76,7 +77,7 @@ public class MessageDatabase {
|
||||
}
|
||||
|
||||
public static void logDeletedMessagesFor(UUID chatId, UUID userId) {
|
||||
List<Message> messages = MessageDatabase.privateChatHistory(chatId);
|
||||
List<Message> messages = MessageDatabase.privateChatHistory(chatId, userId);
|
||||
for (Message message : messages) {
|
||||
if (!isMessageDeleted(message.getMessage_id(), userId)) {
|
||||
String sql = """
|
||||
@@ -110,6 +111,258 @@ public class MessageDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Message> getMessagesForChat(UUID chatId, String chatType, UUID currentUserId, int offset, int limit) {
|
||||
List<Message> messages = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT * FROM messages m
|
||||
WHERE m.receiver_type = ?
|
||||
AND m.receiver_id = ?
|
||||
AND m.is_deleted_globally = FALSE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM deleted_messages d
|
||||
WHERE d.message_id = m.message_id
|
||||
AND d.user_id = ?
|
||||
)
|
||||
ORDER BY m.send_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, chatType);
|
||||
ps.setObject(2, chatId);
|
||||
ps.setObject(3, currentUserId);
|
||||
ps.setInt(4, limit);
|
||||
ps.setInt(5, offset);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
Message msg = new Message(
|
||||
UUID.fromString(rs.getString("message_id")),
|
||||
UUID.fromString(rs.getString("sender_id")),
|
||||
rs.getString("receiver_type"),
|
||||
UUID.fromString(rs.getString("receiver_id")),
|
||||
rs.getString("content"),
|
||||
rs.getString("message_type"),
|
||||
rs.getTimestamp("send_at").toLocalDateTime(),
|
||||
rs.getString("status"),
|
||||
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
||||
rs.getBoolean("is_edited"),
|
||||
rs.getBoolean("is_deleted_globally"),
|
||||
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
||||
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
||||
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null,
|
||||
rs.getTimestamp("edited_at") != null
|
||||
? rs.getTimestamp("edited_at").toLocalDateTime()
|
||||
: null
|
||||
|
||||
|
||||
);
|
||||
|
||||
messages.add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public static Message findById(UUID messageId) {
|
||||
String sql = "SELECT * FROM messages WHERE message_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return new Message(
|
||||
UUID.fromString(rs.getString("message_id")),
|
||||
UUID.fromString(rs.getString("sender_id")),
|
||||
rs.getString("receiver_type"),
|
||||
UUID.fromString(rs.getString("receiver_id")),
|
||||
rs.getString("content"),
|
||||
rs.getString("message_type"),
|
||||
rs.getTimestamp("send_at").toLocalDateTime(),
|
||||
rs.getString("status"),
|
||||
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
||||
rs.getBoolean("is_edited"),
|
||||
rs.getBoolean("is_deleted_globally"),
|
||||
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
||||
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
||||
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null
|
||||
);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean markAsDeletedForUser(UUID messageId, UUID userId) {
|
||||
String sql = "INSERT INTO deleted_messages (message_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ps.setObject(2, userId);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean markAsGloballyDeleted(UUID messageId) {
|
||||
String sql = "UPDATE messages SET is_deleted_globally = true WHERE message_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean updateContentAndMarkEdited(UUID messageId, String newContent) {
|
||||
String sql = """
|
||||
UPDATE messages
|
||||
SET content = ?, is_edited = TRUE, edited_at = CURRENT_TIMESTAMP
|
||||
WHERE message_id = ?
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, newContent);
|
||||
ps.setObject(2, messageId);
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean saveReplyMessage(Message message) {
|
||||
String sql = """
|
||||
INSERT INTO messages (
|
||||
message_id, sender_id, receiver_type, receiver_id, content, message_type,
|
||||
send_at, status, reply_to_id, is_edited, is_deleted_globally,
|
||||
original_message_id, forwarded_by, forwarded_from, edited_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, message.getMessage_id());
|
||||
ps.setObject(2, message.getSender_id());
|
||||
ps.setString(3, message.getReceiver_type());
|
||||
ps.setObject(4, message.getReceiver_id());
|
||||
ps.setString(5, message.getContent());
|
||||
ps.setString(6, message.getMessage_type());
|
||||
ps.setTimestamp(7, Timestamp.valueOf(message.getSend_at()));
|
||||
ps.setString(8, message.getStatus());
|
||||
ps.setObject(9, message.getReply_to_id()); //Important part because of reply message id
|
||||
ps.setBoolean(10, message.isIs_edited());
|
||||
ps.setBoolean(11, message.isIs_deleted_globally());
|
||||
ps.setObject(12, message.getOriginal_message_id());
|
||||
ps.setObject(13, message.getForwarded_by());
|
||||
ps.setObject(14, message.getForwarded_from());
|
||||
ps.setTimestamp(15, message.getEdited_at() != null ? Timestamp.valueOf(message.getEdited_at()) : null);
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean saveForwardedMessage(Message message) {
|
||||
String sql = """
|
||||
INSERT INTO messages (
|
||||
message_id, sender_id, receiver_type, receiver_id,
|
||||
content, message_type, send_at, status,
|
||||
reply_to_id, is_edited, is_deleted_globally,
|
||||
original_message_id, forwarded_by, forwarded_from
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, message.getMessage_id());
|
||||
ps.setObject(2, message.getSender_id());
|
||||
ps.setString(3, message.getReceiver_type());
|
||||
ps.setObject(4, message.getReceiver_id());
|
||||
ps.setString(5, message.getContent());
|
||||
ps.setString(6, message.getMessage_type());
|
||||
ps.setTimestamp(7, Timestamp.valueOf(message.getSend_at()));
|
||||
ps.setString(8, message.getStatus());
|
||||
ps.setObject(9, message.getReply_to_id());
|
||||
ps.setBoolean(10, message.isIs_edited());
|
||||
ps.setBoolean(11, message.isIs_deleted_globally());
|
||||
ps.setObject(12, message.getOriginal_message_id()); //original id
|
||||
ps.setObject(13, message.getForwarded_by()); //forwarded by
|
||||
ps.setObject(14, message.getForwarded_from()); //forwarded from
|
||||
|
||||
return ps.executeUpdate() > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getExcerpt(UUID messageId) {
|
||||
return getExcerpt(messageId, 80);
|
||||
}
|
||||
|
||||
public static String getExcerpt(UUID messageId, int maxLen) {
|
||||
String sql = "SELECT content, message_type, is_deleted_globally FROM messages WHERE message_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, messageId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return "Message isn't available";
|
||||
|
||||
String content = rs.getString("content");
|
||||
String type = rs.getString("message_type"); // e.g. TEXT, IMAGE, VIDEO, AUDIO, FILE, STICKER...
|
||||
boolean deleted = rs.getBoolean("is_deleted_globally");
|
||||
|
||||
if (deleted) return "This message was deleted";
|
||||
|
||||
if (type == null || type.equalsIgnoreCase("TEXT")) {
|
||||
if (content == null || content.isBlank()) return "(empty)";
|
||||
return shorten(content, maxLen);
|
||||
}
|
||||
|
||||
//for media
|
||||
switch (type.toUpperCase()) {
|
||||
case "IMAGE": return "[Photo]";
|
||||
case "VIDEO": return "[Video]";
|
||||
case "AUDIO": return "[Audio]";
|
||||
case "VOICE": return "[Voice]";
|
||||
case "FILE": return "[File]";
|
||||
case "STICKER":return "[Sticker]";
|
||||
default: return "[" + type + "]";
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
private static String shorten(String s, int maxLen) {
|
||||
if (s.length() <= maxLen) return s;
|
||||
return s.substring(0, Math.max(0, maxLen - 1)).trim() + "…";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -128,38 +381,43 @@ public class MessageDatabase {
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<Message> getUnreadMessages(UUID userId) {
|
||||
List<Message> messages = new ArrayList<>();
|
||||
|
||||
String sql = """
|
||||
SELECT m.* FROM messages m
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
LEFT JOIN message_receipts r ON m.message_id = r.message_id AND r.user_id = ?
|
||||
LEFT JOIN deleted_messages d ON m.message_id = d.message_id AND d.user_id = ?
|
||||
WHERE r.user_id IS NULL
|
||||
AND (
|
||||
(m.receiver_type = 'private' AND m.receiver_id = ?)
|
||||
OR
|
||||
(m.receiver_type = 'group' AND EXISTS (
|
||||
SELECT 1 FROM group_members gm WHERE gm.group_id = m.receiver_id AND gm.user_id = ?
|
||||
))
|
||||
OR
|
||||
(m.receiver_type = 'channel' AND EXISTS (
|
||||
SELECT 1 FROM channel_subscribers cs WHERE cs.channel_id = m.receiver_id AND cs.user_id = ?
|
||||
))
|
||||
)
|
||||
AND d.message_id IS NULL
|
||||
AND m.is_deleted_globally = FALSE
|
||||
AND (
|
||||
(m.receiver_type = 'private' AND m.receiver_id = ?)
|
||||
OR
|
||||
(m.receiver_type = 'group' AND EXISTS (
|
||||
SELECT 1 FROM group_members gm WHERE gm.group_id = m.receiver_id AND gm.user_id = ?
|
||||
))
|
||||
OR
|
||||
(m.receiver_type = 'channel' AND EXISTS (
|
||||
SELECT 1 FROM channel_subscribers cs WHERE cs.channel_id = m.receiver_id AND cs.user_id = ?
|
||||
))
|
||||
)
|
||||
ORDER BY m.send_at DESC
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setObject(3, userId);
|
||||
stmt.setObject(4, userId);
|
||||
stmt.setObject(1, userId); // for message_receipts
|
||||
stmt.setObject(2, userId); // for deleted_messages
|
||||
stmt.setObject(3, userId); // for private messages
|
||||
stmt.setObject(4, userId); // for group members
|
||||
stmt.setObject(5, userId); // for channel subscribers
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
messages.add(new Message(
|
||||
Message message = new Message(
|
||||
UUID.fromString(rs.getString("message_id")),
|
||||
rs.getObject("sender_id") != null ? UUID.fromString(rs.getString("sender_id")) : null,
|
||||
rs.getString("receiver_type"),
|
||||
@@ -170,12 +428,15 @@ public class MessageDatabase {
|
||||
rs.getString("status"),
|
||||
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
||||
rs.getBoolean("is_edited"),
|
||||
rs.getBoolean("is_deleted_globally"),
|
||||
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
||||
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
||||
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null,
|
||||
rs.getBoolean("is_deleted_globally"),
|
||||
rs.getTimestamp("edited_at") != null ? rs.getTimestamp("edited_at").toLocalDateTime() : null
|
||||
));
|
||||
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
@@ -185,6 +446,8 @@ public class MessageDatabase {
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<FileAttachment> getAttachments(UUID messageId) {
|
||||
List<FileAttachment> attachments = new ArrayList<>();
|
||||
String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?";
|
||||
@@ -283,45 +546,129 @@ public class MessageDatabase {
|
||||
|
||||
|
||||
|
||||
|
||||
// public static List<Message> searchMessagesForUser(UUID userId, String keyword) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'private'
|
||||
// AND (sender_id = ? OR receiver_id = ?)
|
||||
// AND content ILIKE ?
|
||||
// ORDER BY send_at DESC
|
||||
// """;
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// stmt.setObject(1, userId);
|
||||
// stmt.setObject(2, userId);
|
||||
// stmt.setString(3, "%" + keyword + "%");
|
||||
//
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
// }
|
||||
|
||||
public static List<Message> searchMessagesForUser(UUID userId, String keyword) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'private'
|
||||
AND (sender_id = ? OR receiver_id = ?)
|
||||
AND content ILIKE ?
|
||||
ORDER BY send_at DESC
|
||||
""";
|
||||
SELECT * FROM messages m
|
||||
WHERE m.receiver_type = 'private'
|
||||
AND m.content ILIKE ?
|
||||
AND m.is_deleted_globally = FALSE
|
||||
AND (m.sender_id = ? OR m.receiver_id = ?)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM deleted_messages d
|
||||
WHERE d.message_id = m.message_id AND d.user_id = ?
|
||||
)
|
||||
ORDER BY m.send_at DESC
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, userId);
|
||||
stmt.setObject(2, userId);
|
||||
stmt.setString(3, "%" + keyword + "%");
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
ps.setString(1, "%" + keyword + "%");
|
||||
ps.setObject(2, userId); // m.sender_id
|
||||
ps.setObject(3, userId); // m.receiver_id
|
||||
ps.setObject(4, userId); // deleted_messages
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
List<Message> messages = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
messages.add(mapResultSetToMessage(rs));
|
||||
}
|
||||
return messages;
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Message> privateChatHistory(UUID chatId) {
|
||||
private static Message mapResultSetToMessage(ResultSet rs) throws SQLException {
|
||||
return new Message(
|
||||
UUID.fromString(rs.getString("message_id")),
|
||||
rs.getObject("sender_id") != null ? UUID.fromString(rs.getString("sender_id")) : null,
|
||||
rs.getString("receiver_type"),
|
||||
UUID.fromString(rs.getString("receiver_id")),
|
||||
rs.getString("content"),
|
||||
rs.getString("message_type"),
|
||||
rs.getTimestamp("send_at").toLocalDateTime(),
|
||||
rs.getString("status"),
|
||||
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
|
||||
rs.getBoolean("is_edited"),
|
||||
rs.getBoolean("is_deleted_globally"),
|
||||
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
|
||||
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
|
||||
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public static List<Message> privateChatHistory(UUID chatId) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'private'
|
||||
// AND receiver_id = ?
|
||||
// ORDER BY send_at
|
||||
// """;
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// stmt.setObject(1, chatId);
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
public static List<Message> privateChatHistory(UUID chatId, UUID userId) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'private'
|
||||
AND receiver_id = ?
|
||||
AND receiver_id = ?
|
||||
AND is_deleted_globally = FALSE
|
||||
AND message_id NOT IN (
|
||||
SELECT message_id FROM deleted_messages WHERE user_id = ?
|
||||
)
|
||||
ORDER BY send_at
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, chatId);
|
||||
stmt.setObject(2, userId);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
@@ -335,17 +682,47 @@ public class MessageDatabase {
|
||||
|
||||
|
||||
|
||||
public static List<Message> groupChatHistory(UUID groupId) {
|
||||
|
||||
// public static List<Message> groupChatHistory(UUID groupId) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'group' AND receiver_id = ?
|
||||
// ORDER BY send_at
|
||||
// """;
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// stmt.setObject(1, groupId);
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
public static List<Message> groupChatHistory(UUID groupId,UUID userId) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'group' AND receiver_id = ?
|
||||
WHERE receiver_type = 'group'
|
||||
AND receiver_id = ?
|
||||
AND is_deleted_globally = FALSE
|
||||
AND message_id NOT IN (
|
||||
SELECT message_id FROM deleted_messages WHERE user_id = ?
|
||||
)
|
||||
ORDER BY send_at
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
stmt.setObject(1, groupId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
@@ -357,17 +734,48 @@ public class MessageDatabase {
|
||||
}
|
||||
|
||||
|
||||
public static List<Message> channelChatHistory(UUID channelId) {
|
||||
|
||||
// public static List<Message> channelChatHistory(UUID channelId) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'channel' AND receiver_id = ?
|
||||
// ORDER BY send_at
|
||||
// """;
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
// stmt.setObject(1, channelId);
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
public static List<Message> channelChatHistory(UUID channelId, UUID userId) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'channel' AND receiver_id = ?
|
||||
WHERE receiver_type = 'channel'
|
||||
AND receiver_id = ?
|
||||
AND is_deleted_globally = FALSE
|
||||
AND message_id NOT IN (
|
||||
SELECT message_id FROM deleted_messages WHERE user_id = ?
|
||||
)
|
||||
ORDER BY send_at
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
stmt.setObject(1, channelId);
|
||||
stmt.setObject(2, userId);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
@@ -379,32 +787,75 @@ public class MessageDatabase {
|
||||
}
|
||||
|
||||
|
||||
public static List<Message> searchMessagesInGroups(List<UUID> groupIds, String keyword) {
|
||||
|
||||
// public static List<Message> searchMessagesInGroups(List<UUID> groupIds, String keyword) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// if (groupIds.isEmpty()) return result;
|
||||
//
|
||||
// String placeholders = groupIds.stream().map(id -> "?").collect(Collectors.joining(", "));
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'group'
|
||||
// AND receiver_id IN (%s)
|
||||
// AND content ILIKE ?
|
||||
// ORDER BY send_at DESC
|
||||
// """.formatted(placeholders);
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
//
|
||||
// int i = 1;
|
||||
// for (UUID id : groupIds) {
|
||||
// stmt.setObject(i++, id);
|
||||
// }
|
||||
// stmt.setString(i, "%" + keyword + "%");
|
||||
//
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
public static List<Message> searchMessagesInGroups(List<UUID> groupIds, String keyword, UUID userId) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
if (groupIds.isEmpty()) return result;
|
||||
|
||||
String placeholders = groupIds.stream().map(id -> "?").collect(Collectors.joining(", "));
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'group'
|
||||
AND receiver_id IN (%s)
|
||||
AND content ILIKE ?
|
||||
ORDER BY send_at DESC
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
LEFT JOIN deleted_messages d ON m.message_id = d.message_id AND d.user_id = ?
|
||||
WHERE m.receiver_type = 'group'
|
||||
AND m.receiver_id IN (%s)
|
||||
AND d.message_id IS NULL
|
||||
AND m.is_deleted_globally = FALSE
|
||||
AND m.content ILIKE ?
|
||||
ORDER BY m.send_at DESC
|
||||
""".formatted(placeholders);
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
int i = 1;
|
||||
for (UUID id : groupIds) {
|
||||
stmt.setObject(i++, id);
|
||||
stmt.setObject(i++, userId);
|
||||
for (UUID groupId : groupIds) {
|
||||
stmt.setObject(i++, groupId);
|
||||
}
|
||||
|
||||
stmt.setString(i, "%" + keyword + "%");
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
result.add(mapResultSetToMessage(rs));
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -413,32 +864,71 @@ public class MessageDatabase {
|
||||
}
|
||||
|
||||
|
||||
// public static List<Message> searchMessagesInChannels(List<UUID> channelIds, String keyword) {
|
||||
// List<Message> result = new ArrayList<>();
|
||||
// if (channelIds.isEmpty()) return result;
|
||||
//
|
||||
// String placeholders = channelIds.stream().map(id -> "?").collect(Collectors.joining(", "));
|
||||
// String sql = """
|
||||
// SELECT * FROM messages
|
||||
// WHERE receiver_type = 'channel'
|
||||
// AND receiver_id IN (%s)
|
||||
// AND content ILIKE ?
|
||||
// ORDER BY send_at DESC
|
||||
// """.formatted(placeholders);
|
||||
//
|
||||
// try (Connection conn = ConnectionDb.connect();
|
||||
// PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
//
|
||||
// int i = 1;
|
||||
// for (UUID id : channelIds) {
|
||||
// stmt.setObject(i++, id);
|
||||
// }
|
||||
// stmt.setString(i, "%" + keyword + "%");
|
||||
//
|
||||
// ResultSet rs = stmt.executeQuery();
|
||||
// while (rs.next()) {
|
||||
// result.add(extractMessage(rs));
|
||||
// }
|
||||
// } catch (SQLException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
public static List<Message> searchMessagesInChannels(List<UUID> channelIds, String keyword) {
|
||||
List<Message> result = new ArrayList<>();
|
||||
if (channelIds.isEmpty()) return result;
|
||||
|
||||
String placeholders = channelIds.stream().map(id -> "?").collect(Collectors.joining(", "));
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_type = 'channel'
|
||||
AND receiver_id IN (%s)
|
||||
AND content ILIKE ?
|
||||
ORDER BY send_at DESC
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
WHERE m.receiver_type = 'channel'
|
||||
AND m.receiver_id IN (%s)
|
||||
AND m.is_deleted_globally = FALSE
|
||||
AND m.content ILIKE ?
|
||||
ORDER BY m.send_at DESC
|
||||
""".formatted(placeholders);
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
|
||||
int i = 1;
|
||||
for (UUID id : channelIds) {
|
||||
stmt.setObject(i++, id);
|
||||
for (UUID channelId : channelIds) {
|
||||
stmt.setObject(i++, channelId);
|
||||
}
|
||||
|
||||
stmt.setString(i, "%" + keyword + "%");
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.add(extractMessage(rs));
|
||||
result.add(mapResultSetToMessage(rs));
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -484,6 +974,37 @@ public class MessageDatabase {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public static List<Message> getMessages(UUID chatId, UUID userId) {
|
||||
String sql = """
|
||||
SELECT * FROM messages
|
||||
WHERE receiver_id = ?
|
||||
AND is_deleted_globally = false
|
||||
AND message_id NOT IN (
|
||||
SELECT message_id FROM deleted_messages
|
||||
WHERE user_id = ?
|
||||
)
|
||||
ORDER BY send_at
|
||||
""";
|
||||
|
||||
List<Message> messages = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, chatId);
|
||||
ps.setObject(2, userId);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
Message m = extractMessage(rs);
|
||||
messages.add(m);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public static boolean insertSavedMessage(Message message) {
|
||||
String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type, send_at, status, reply_to_id, is_edited, edited_at," +
|
||||
" original_message_id, forwarded_by, forwarded_from, is_deleted_globally) " +
|
||||
@@ -537,5 +1058,4 @@ public class MessageDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.to.telegramfinalproject.Database;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MessageReactionDatabase {
|
||||
|
||||
public static boolean saveOrUpdateReaction(UUID messageId, UUID userId, String reaction) {
|
||||
String sql = """
|
||||
INSERT INTO message_reactions (message_id, user_id, emoji)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT (message_id, user_id)
|
||||
DO UPDATE SET emoji = EXCLUDED.emoji, reacted_at = CURRENT_TIMESTAMP
|
||||
""";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ps.setObject(2, userId);
|
||||
ps.setString(3, reaction);
|
||||
return ps.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getReactions(UUID messageId) {
|
||||
String sql = "SELECT emoji FROM message_reactions WHERE message_id = ?";
|
||||
List<String> reactions = new ArrayList<>();
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, messageId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
reactions.add(rs.getString("emoji"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return reactions;
|
||||
}
|
||||
public static JSONObject getCountsAsJson(UUID messageId) {
|
||||
String sql = """
|
||||
SELECT emoji, COUNT(*) AS c
|
||||
FROM message_reactions
|
||||
WHERE message_id = ?
|
||||
GROUP BY emoji
|
||||
""";
|
||||
|
||||
JSONObject counts = new JSONObject();
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setObject(1, messageId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String emoji = rs.getString("emoji");
|
||||
int c = rs.getInt("c");
|
||||
counts.put(emoji, c);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return counts; // مثال: {"❤️":2,"👍":1}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -283,7 +283,36 @@ public class PrivateChatDatabase {
|
||||
}
|
||||
|
||||
|
||||
public static UUID findChatBetween(UUID user1, UUID user2) {
|
||||
String sql = """
|
||||
SELECT chat_id FROM private_chat
|
||||
WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)
|
||||
""";
|
||||
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setObject(1, user1);
|
||||
ps.setObject(2, user2);
|
||||
ps.setObject(3, user2);
|
||||
ps.setObject(4, user1);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
return (UUID) rs.getObject("chat_id");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static UUID getOtherParticipant(UUID chatId, UUID me) {
|
||||
List<UUID> members = getMembers(chatId);
|
||||
for (UUID u : members) {
|
||||
if (!u.equals(me)) return u;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,5 +40,4 @@ public class Contact {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import org.json.JSONObject;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ContactEntry {
|
||||
@@ -71,4 +72,16 @@ public class ContactEntry {
|
||||
public String toString() {
|
||||
return profileName + " (@" + contact_displayId + ")" + (isBlocked ? " [Blocked]" : "") + " Last Seen:" + (lastSeenTime != null ? " " + lastSeenTime.toString() : "");
|
||||
}
|
||||
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("contact_id", contactId.toString());
|
||||
obj.put("user_id", userId);
|
||||
obj.put("profile_name", profileName);
|
||||
obj.put("image_url", imageUrl);
|
||||
obj.put("is_blocked", isBlocked);
|
||||
return obj;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.to.telegramfinalproject.Models;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -9,20 +10,43 @@ import java.util.UUID;
|
||||
|
||||
public class JsonUtil {
|
||||
public static JSONArray contactListToJson(List<Contact> contacts) {
|
||||
|
||||
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
for (Contact contact : contacts) {
|
||||
UUID contactId = contact.getContact_id();
|
||||
User contactUser = userDatabase.findByInternalUUID(contactId);
|
||||
if (contactUser == null) continue;
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("user_id", contact.getUser_id().toString());
|
||||
obj.put("contact_id", contact.getContact_id().toString());
|
||||
obj.put("contact_id", contactId.toString()); // UUID
|
||||
obj.put("user_id", contactUser.getUser_id()); // public ID
|
||||
obj.put("profile_name", contactUser.getProfile_name());
|
||||
obj.put("image_url", contactUser.getImage_url());
|
||||
obj.put("is_blocked", contact.getIs_blocked());
|
||||
obj.put("added_at", contact.getAdd_at().toString());
|
||||
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static JSONArray contactEntryListToJson(List<ContactEntry> contactEntries) {
|
||||
JSONArray array = new JSONArray();
|
||||
|
||||
for (ContactEntry entry : contactEntries) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("contact_id", entry.getContactId().toString());
|
||||
obj.put("user_id", entry.getUserId());
|
||||
obj.put("profile_name", entry.getProfileName());
|
||||
obj.put("image_url", entry.getImageUrl());
|
||||
obj.put("is_blocked", entry.isBlocked());
|
||||
array.put(obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package org.to.telegramfinalproject.Models;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Message {
|
||||
private final UUID message_id;
|
||||
private UUID message_id;
|
||||
private UUID sender_id;
|
||||
private String receiver_type; // private, group, channel
|
||||
private String receiver_type;
|
||||
private UUID receiver_id;
|
||||
private String content;
|
||||
private String message_type; // TEXT, IMAGE, FILE, ...
|
||||
private String message_type;
|
||||
private LocalDateTime send_at;
|
||||
private String status; // SENT, DELIVERED, READ
|
||||
private String status;
|
||||
private UUID reply_to_id;
|
||||
private boolean is_edited;
|
||||
private UUID original_message_id;
|
||||
@@ -21,6 +22,30 @@ public class Message {
|
||||
private List<FileAttachment> attachments;
|
||||
private boolean is_deleted_globally;
|
||||
private LocalDateTime edited_at;
|
||||
private transient String sender_name;
|
||||
private transient String receiver_name;
|
||||
|
||||
|
||||
// ✅ Full Constructor
|
||||
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
|
||||
String message_type, LocalDateTime send_at, String status,
|
||||
UUID reply_to_id, boolean is_edited, boolean is_deleted_globally,
|
||||
UUID original_message_id, UUID forwarded_by, UUID forwarded_from) {
|
||||
this.message_id = message_id;
|
||||
this.sender_id = sender_id;
|
||||
this.receiver_type = receiver_type;
|
||||
this.receiver_id = receiver_id;
|
||||
this.content = content;
|
||||
this.message_type = message_type;
|
||||
this.send_at = send_at;
|
||||
this.status = status;
|
||||
this.reply_to_id = reply_to_id;
|
||||
this.is_edited = is_edited;
|
||||
this.is_deleted_globally = is_deleted_globally;
|
||||
this.original_message_id = original_message_id;
|
||||
this.forwarded_by = forwarded_by;
|
||||
this.forwarded_from = forwarded_from;
|
||||
}
|
||||
|
||||
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
|
||||
String message_type, LocalDateTime send_at, String status,
|
||||
@@ -43,123 +68,134 @@ public class Message {
|
||||
this.edited_at = edited_at;
|
||||
}
|
||||
|
||||
public Message(UUID messageId, UUID senderId, UUID receiverId, String receiverType, String content, String messageType, LocalDateTime now) {
|
||||
|
||||
// ✅ Short Constructors
|
||||
//for normal messages
|
||||
public Message(UUID messageId, UUID senderId, UUID receiverId, String receiverType,
|
||||
String content, String messageType, LocalDateTime sendAt) {
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_id = receiverId;
|
||||
this.receiver_type = receiverType;
|
||||
this.content = content;
|
||||
this.message_type = messageType;
|
||||
this.send_at = now;
|
||||
this.send_at = sendAt;
|
||||
}
|
||||
|
||||
|
||||
public UUID getMessage_id() {
|
||||
return message_id;
|
||||
}
|
||||
|
||||
|
||||
public UUID getSender_id() {
|
||||
return sender_id;
|
||||
}
|
||||
|
||||
public void setSender_id(UUID sender_id) {
|
||||
this.sender_id = sender_id;
|
||||
}
|
||||
|
||||
public String getReceiver_type() {
|
||||
return receiver_type;
|
||||
}
|
||||
|
||||
public void setReceiver_type(String receiver_type) {
|
||||
this.receiver_type = receiver_type;
|
||||
}
|
||||
|
||||
public UUID getReceiver_id() {
|
||||
return receiver_id;
|
||||
}
|
||||
|
||||
public void setReceiver_id(UUID receiver_id) {
|
||||
this.receiver_id = receiver_id;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
//for reply messages
|
||||
public Message(UUID messageId, UUID senderId, String receiverType, UUID receiverId,
|
||||
String content, String messageType, LocalDateTime sendAt, String status,
|
||||
UUID replyToId) {
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_type = receiverType;
|
||||
this.receiver_id = receiverId;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getMessage_type() {
|
||||
return message_type;
|
||||
}
|
||||
|
||||
public void setMessage_type(String message_type) {
|
||||
this.message_type = message_type;
|
||||
}
|
||||
|
||||
public LocalDateTime getSend_at() {
|
||||
return send_at;
|
||||
}
|
||||
|
||||
public void setSend_at(LocalDateTime send_at) {
|
||||
this.send_at = send_at;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.message_type = messageType;
|
||||
this.send_at = sendAt;
|
||||
this.status = status;
|
||||
this.reply_to_id = replyToId;
|
||||
this.is_edited = false;
|
||||
this.is_deleted_globally = false;
|
||||
}
|
||||
|
||||
public UUID getReply_to_id() {
|
||||
return reply_to_id;
|
||||
|
||||
|
||||
|
||||
public Message(UUID messageId, UUID senderId, String receiverType, UUID receiverId,
|
||||
String content, String messageType, LocalDateTime sendAt, String status,
|
||||
UUID replyToId, boolean isEdited, boolean isDeletedGlobally,
|
||||
UUID originalMessageId, UUID forwardedBy, UUID forwardedFrom,
|
||||
LocalDateTime editedAt) {
|
||||
|
||||
this.message_id = messageId;
|
||||
this.sender_id = senderId;
|
||||
this.receiver_type = receiverType;
|
||||
this.receiver_id = receiverId;
|
||||
this.content = content;
|
||||
this.message_type = messageType;
|
||||
this.send_at = sendAt;
|
||||
this.status = status;
|
||||
this.reply_to_id = replyToId;
|
||||
this.is_edited = isEdited;
|
||||
this.is_deleted_globally = isDeletedGlobally;
|
||||
this.original_message_id = originalMessageId;
|
||||
this.forwarded_by = forwardedBy;
|
||||
this.forwarded_from = forwardedFrom;
|
||||
this.edited_at = editedAt;
|
||||
}
|
||||
|
||||
public void setReply_to_id(UUID reply_to_id) {
|
||||
this.reply_to_id = reply_to_id;
|
||||
|
||||
|
||||
// ✅ Getters & Setters
|
||||
public UUID getMessage_id() { return message_id; }
|
||||
public void setMessage_id(UUID messageId) { this.message_id = messageId; }
|
||||
|
||||
public UUID getSender_id() { return sender_id; }
|
||||
public void setSender_id(UUID sender_id) { this.sender_id = sender_id; }
|
||||
|
||||
public String getReceiver_type() { return receiver_type; }
|
||||
public void setReceiver_type(String receiver_type) { this.receiver_type = receiver_type; }
|
||||
|
||||
public UUID getReceiver_id() { return receiver_id; }
|
||||
public void setReceiver_id(UUID receiver_id) { this.receiver_id = receiver_id; }
|
||||
|
||||
public String getContent() { return content; }
|
||||
public void setContent(String content) { this.content = content; }
|
||||
|
||||
public String getMessage_type() { return message_type; }
|
||||
public void setMessage_type(String message_type) { this.message_type = message_type; }
|
||||
|
||||
public LocalDateTime getSend_at() { return send_at; }
|
||||
public void setSend_at(LocalDateTime send_at) { this.send_at = send_at; }
|
||||
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
|
||||
public UUID getReply_to_id() { return reply_to_id; }
|
||||
public void setReply_to_id(UUID reply_to_id) { this.reply_to_id = reply_to_id; }
|
||||
|
||||
public boolean isIs_edited() { return is_edited; }
|
||||
public void setIs_edited(boolean is_edited) { this.is_edited = is_edited; }
|
||||
|
||||
public boolean isIs_deleted_globally() { return is_deleted_globally; }
|
||||
public void setIs_deleted_globally(boolean is_deleted_globally) {
|
||||
this.is_deleted_globally = is_deleted_globally;
|
||||
}
|
||||
|
||||
public boolean isIs_edited() {
|
||||
return is_edited;
|
||||
public UUID getOriginal_message_id() { return original_message_id; }
|
||||
public void setOriginal_message_id(UUID original_message_id) { this.original_message_id = original_message_id; }
|
||||
|
||||
public UUID getForwarded_by() { return forwarded_by; }
|
||||
public void setForwarded_by(UUID forwarded_by) { this.forwarded_by = forwarded_by; }
|
||||
|
||||
public UUID getForwarded_from() { return forwarded_from; }
|
||||
public void setForwarded_from(UUID forwarded_from) { this.forwarded_from = forwarded_from; }
|
||||
|
||||
public List<FileAttachment> getAttachments() { return attachments; }
|
||||
public void setAttachments(List<FileAttachment> attachments) { this.attachments = attachments; }
|
||||
public String getSender_name() {
|
||||
return sender_name;
|
||||
}
|
||||
|
||||
public void setIs_edited(boolean is_edited) {
|
||||
this.is_edited = is_edited;
|
||||
public void setSender_name(String sender_name) {
|
||||
this.sender_name = sender_name;
|
||||
}
|
||||
|
||||
public UUID getOriginal_message_id() {
|
||||
return original_message_id;
|
||||
public String getReceiver_name() {
|
||||
return receiver_name;
|
||||
}
|
||||
|
||||
public void setOriginal_message_id(UUID original_message_id) {
|
||||
this.original_message_id = original_message_id;
|
||||
public void setReceiver_name(String receiver_name) {
|
||||
this.receiver_name = receiver_name;
|
||||
}
|
||||
|
||||
public UUID getForwarded_by() {
|
||||
return forwarded_by;
|
||||
public LocalDateTime getEdited_at() {
|
||||
return edited_at;
|
||||
}
|
||||
|
||||
public void setForwarded_by(UUID forwarded_by) {
|
||||
this.forwarded_by = forwarded_by;
|
||||
}
|
||||
|
||||
public UUID getForwarded_from() {
|
||||
return forwarded_from;
|
||||
}
|
||||
|
||||
public void setForwarded_from(UUID forwarded_from) {
|
||||
this.forwarded_from = forwarded_from;
|
||||
}
|
||||
|
||||
public void setAttachments(List<FileAttachment> attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
public List<FileAttachment> getAttachments() {
|
||||
return attachments;
|
||||
public void setEdited_at(LocalDateTime edited_at) {
|
||||
this.edited_at = edited_at;
|
||||
}
|
||||
|
||||
public void setIs_deleted_globally(boolean is_deleted_globally) {
|
||||
|
||||
@@ -427,10 +427,14 @@ public class ClientHandler implements Runnable {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("type", "message");
|
||||
obj.put("content", m.getContent());
|
||||
obj.put("sender", m.getSender_id().toString());
|
||||
obj.put("time", m.getSend_at().toString());
|
||||
obj.put("sender", m.getSender_id() != null ? m.getSender_id().toString() : "N/A");
|
||||
obj.put("receiver_id", m.getReceiver_id().toString());
|
||||
obj.put("receiver_type", m.getReceiver_type());
|
||||
User senderUser = userDatabase.findByInternalUUID(m.getSender_id());
|
||||
String senderName = senderUser != null ? senderUser.getProfile_name() : "Unknown";
|
||||
obj.put("sender_name", senderName);
|
||||
|
||||
results.add(obj);
|
||||
}
|
||||
|
||||
@@ -447,13 +451,23 @@ public class ClientHandler implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
List<Message> groupMessages = MessageDatabase.searchMessagesInGroups(groupIds, keyword);
|
||||
List<Message> groupMessages = MessageDatabase.searchMessagesInGroups(groupIds, keyword, currentUser.getInternal_uuid());
|
||||
for (Message m : groupMessages) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("type", "message");
|
||||
obj.put("from", "group");
|
||||
obj.put("content", m.getContent());
|
||||
obj.put("time", m.getSend_at().toString());
|
||||
obj.put("sender", m.getSender_id() != null ? m.getSender_id().toString() : "N/A");
|
||||
obj.put("receiver_id", m.getReceiver_id().toString());
|
||||
obj.put("receiver_type", "group");
|
||||
Group senderGroup = GroupDatabase.findByInternalUUID(m.getReceiver_id());
|
||||
String groupName = senderGroup != null ? senderGroup.getGroup_name() : "Unknown";
|
||||
obj.put("group_name", groupName);
|
||||
User senderUser = userDatabase.findByInternalUUID(m.getSender_id());
|
||||
String senderName = senderUser != null ? senderUser.getProfile_name() : "Unknown";
|
||||
obj.put("sender_name", senderName);
|
||||
|
||||
results.add(obj);
|
||||
}
|
||||
|
||||
@@ -464,6 +478,15 @@ public class ClientHandler implements Runnable {
|
||||
obj.put("from", "channel");
|
||||
obj.put("content", m.getContent());
|
||||
obj.put("time", m.getSend_at().toString());
|
||||
obj.put("sender", m.getSender_id() != null ? m.getSender_id().toString() : "N/A");
|
||||
obj.put("receiver_id", m.getReceiver_id().toString());
|
||||
obj.put("receiver_type", "channel");
|
||||
Channel SenderChannel = ChannelDatabase.findByInternalUUID(m.getReceiver_id());
|
||||
String channelName = SenderChannel != null ? SenderChannel.getChannel_name() : "Unknown";
|
||||
obj.put("channel_name", channelName);
|
||||
User senderUser = userDatabase.findByInternalUUID(m.getSender_id());
|
||||
String senderName = senderUser != null ? senderUser.getProfile_name() : "Unknown";
|
||||
obj.put("sender_name", senderName);
|
||||
results.add(obj);
|
||||
}
|
||||
|
||||
@@ -494,6 +517,7 @@ public class ClientHandler implements Runnable {
|
||||
currentUser.getImage_url(),
|
||||
contactUUID
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
response = success
|
||||
@@ -1280,7 +1304,7 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
messages = MessageDatabase.privateChatHistory(chatId);
|
||||
messages = MessageDatabase.privateChatHistory(chatId, currentUser.getInternal_uuid());
|
||||
}
|
||||
|
||||
|
||||
@@ -1290,7 +1314,7 @@ public class ClientHandler implements Runnable {
|
||||
response = new ResponseModel("error", "Group not found.");
|
||||
break;
|
||||
}
|
||||
messages = MessageDatabase.groupChatHistory(group.getInternal_uuid());
|
||||
messages = MessageDatabase.groupChatHistory(group.getInternal_uuid(),currentUser.getInternal_uuid());
|
||||
}
|
||||
|
||||
|
||||
@@ -1300,7 +1324,7 @@ public class ClientHandler implements Runnable {
|
||||
response = new ResponseModel("error", "Channel not found.");
|
||||
break;
|
||||
}
|
||||
messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid());
|
||||
messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid(),currentUser.getInternal_uuid());
|
||||
}
|
||||
default -> {
|
||||
response = new ResponseModel("error", "Invalid receiver type.");
|
||||
@@ -2003,7 +2027,6 @@ public class ClientHandler implements Runnable {
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case "get_or_create_private_chat": {
|
||||
if (currentUser == null) {
|
||||
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||
@@ -2014,7 +2037,23 @@ public class ClientHandler implements Runnable {
|
||||
UUID user1 = UUID.fromString(requestJson.getString("user1"));
|
||||
UUID user2 = UUID.fromString(requestJson.getString("user2"));
|
||||
|
||||
UUID oldChat = PrivateChatDatabase.findChatBetween(user1, user2);
|
||||
UUID chatId = PrivateChatDatabase.getOrCreateChat(user1, user2);
|
||||
boolean isNew = oldChat == null;
|
||||
|
||||
if (isNew) {
|
||||
JSONObject chatPayload = new JSONObject();
|
||||
chatPayload.put("action", "created_private_chat");
|
||||
|
||||
JSONObject chatData = new JSONObject();
|
||||
chatData.put("chat_id", chatId.toString());
|
||||
chatData.put("chat_type", "private");
|
||||
chatData.put("last_message_time", LocalDateTime.now().toString());
|
||||
chatPayload.put("data", chatData);
|
||||
|
||||
RealTimeEventDispatcher.sendToUser(user1, chatPayload);
|
||||
RealTimeEventDispatcher.sendToUser(user2, chatPayload);
|
||||
}
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
@@ -2029,6 +2068,7 @@ public class ClientHandler implements Runnable {
|
||||
|
||||
|
||||
|
||||
|
||||
case "get_private_chat_target": {
|
||||
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
|
||||
userId = currentUser.getInternal_uuid();
|
||||
@@ -2044,6 +2084,346 @@ public class ClientHandler implements Runnable {
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_contact_list": {
|
||||
if (currentUser == null) {
|
||||
response = new ResponseModel("error", "Unauthorized. Please login first.");
|
||||
break;
|
||||
}
|
||||
|
||||
List<Contact> contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid());
|
||||
List<ContactEntry> contactEntries = new ArrayList<>();
|
||||
|
||||
for (Contact contact : contacts) {
|
||||
UUID contactId = contact.getContact_id();
|
||||
User contactUser = userDatabase.findByInternalUUID(contactId);
|
||||
if (contactUser == null) continue;
|
||||
|
||||
ContactEntry entry = new ContactEntry(
|
||||
contactId,
|
||||
contactUser.getUser_id(),
|
||||
contactUser.getProfile_name(),
|
||||
contactUser.getImage_url(),
|
||||
contact.getIs_blocked()
|
||||
);
|
||||
|
||||
contactEntries.add(entry);
|
||||
}
|
||||
|
||||
// Optional: sort alphabetically
|
||||
contactEntries.sort(Comparator.comparing(ContactEntry::getProfileName, String.CASE_INSENSITIVE_ORDER));
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("contact_list", JsonUtil.contactEntryListToJson(contactEntries));
|
||||
|
||||
response = new ResponseModel("success", "Contact list refreshed", data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_chat_messages" : {
|
||||
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
|
||||
String chatType = requestJson.getString("chat_type");
|
||||
int offset = requestJson.optInt("offset", 0);
|
||||
int limit = requestJson.optInt("limit", 10);
|
||||
|
||||
List<Message> messages = MessageDatabase.getMessagesForChat(chatId, chatType, currentUser.getInternal_uuid(), offset, limit);
|
||||
|
||||
JSONArray result = new JSONArray();
|
||||
for (Message m : messages) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("message_id", m.getMessage_id().toString());
|
||||
obj.put("sender_id", m.getSender_id().toString());
|
||||
|
||||
User sender = userDatabase.findByInternalUUID(m.getSender_id());
|
||||
obj.put("sender_name", sender != null ? sender.getProfile_name() : "Unknown");
|
||||
|
||||
obj.put("receiver_id", m.getReceiver_id().toString());
|
||||
obj.put("receiver_type", m.getReceiver_type());
|
||||
|
||||
String receiverName = switch (m.getReceiver_type()) {
|
||||
case "group" -> {
|
||||
Group g = GroupDatabase.findByInternalUUID(m.getReceiver_id());
|
||||
yield g != null ? g.getGroup_name() : "Unknown group";
|
||||
}
|
||||
case "channel" -> {
|
||||
Channel c = ChannelDatabase.findByInternalUUID(m.getReceiver_id());
|
||||
yield c != null ? c.getChannel_name() : "Unknown channel";
|
||||
}
|
||||
case "private" -> {
|
||||
UUID otherId = PrivateChatDatabase.getOtherParticipant(
|
||||
m.getReceiver_id(), // chat_id
|
||||
currentUser.getInternal_uuid() // my user uuid
|
||||
);
|
||||
User other = userDatabase.findByInternalUUID(otherId);
|
||||
yield other != null ? other.getProfile_name() : "Unknown user";
|
||||
}
|
||||
|
||||
default -> "Unknown";
|
||||
};
|
||||
obj.put("receiver_name", receiverName);
|
||||
|
||||
obj.put("content", m.getContent());
|
||||
obj.put("message_type", m.getMessage_type());
|
||||
obj.put("time", m.getSend_at().toString());
|
||||
obj.put("is_edited", m.isIs_edited());
|
||||
obj.put("is_deleted_globally", m.isIs_deleted_globally());
|
||||
obj.put("edited_at", m.getEdited_at() != null ? m.getEdited_at().toString() : JSONObject.NULL);
|
||||
|
||||
//For reply messages
|
||||
if (m.getReply_to_id() != null) {
|
||||
Message replied = MessageDatabase.findById(m.getReply_to_id());
|
||||
if (replied != null) {
|
||||
User repliedSender = userDatabase.findByInternalUUID(replied.getSender_id());
|
||||
obj.put("reply_to_id", replied.getMessage_id().toString());
|
||||
obj.put("reply_to_sender", repliedSender != null ? repliedSender.getProfile_name() : "Unknown");
|
||||
obj.put("reply_to_content", replied.getContent());
|
||||
}
|
||||
}
|
||||
|
||||
// For forwarded messages
|
||||
if (m.getOriginal_message_id() != null && m.getForwarded_from() != null) {
|
||||
User originalSender = userDatabase.findByInternalUUID(m.getForwarded_from());
|
||||
obj.put("is_forwarded", true);
|
||||
obj.put("forwarded_from_id", m.getForwarded_from().toString());
|
||||
obj.put("forwarded_from_name", originalSender != null ? originalSender.getProfile_name() : "Unknown");
|
||||
} else {
|
||||
obj.put("is_forwarded", false);
|
||||
}
|
||||
|
||||
//For reactions
|
||||
List<String> reactions = MessageReactionDatabase.getReactions(m.getMessage_id());
|
||||
obj.put("reactions", new JSONArray(reactions));
|
||||
|
||||
|
||||
result.put(obj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("get_chat_messages", result);
|
||||
|
||||
response = new ResponseModel("success", "get messages ", data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "delete_message" : {
|
||||
UUID messageId = UUID.fromString(requestJson.getString("message_id"));
|
||||
String deleteType = requestJson.getString("delete_type");
|
||||
|
||||
Message msg = MessageDatabase.findById(messageId);
|
||||
if (msg == null) {
|
||||
response = new ResponseModel("error", "Message not found.");
|
||||
break;
|
||||
}
|
||||
|
||||
UUID currentUserId = currentUser.getInternal_uuid();
|
||||
|
||||
if (deleteType.equals("one-sided")) {
|
||||
boolean success = MessageDatabase.markAsDeletedForUser(messageId, currentUserId);
|
||||
if (success)
|
||||
response = new ResponseModel("success", "Message deleted for current user.");
|
||||
else
|
||||
response = new ResponseModel("error", "Failed to delete message for user.");
|
||||
break;
|
||||
}
|
||||
|
||||
//If allowed to delete the message
|
||||
if (deleteType.equals("global")) {
|
||||
boolean allowed = false;
|
||||
|
||||
String type = msg.getReceiver_type();
|
||||
UUID chatId = msg.getReceiver_id();
|
||||
|
||||
if (type.equals("private") || type.equals("group")) {
|
||||
allowed = msg.getSender_id().equals(currentUserId);
|
||||
} else if (type.equals("channel")) {
|
||||
//Only owner and admins can delete messages
|
||||
allowed = ChannelPermissionUtil.canDeleteMessage(chatId, currentUserId);
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
response = new ResponseModel("error", "You are not allowed to delete this message globally.");
|
||||
break;
|
||||
}
|
||||
|
||||
boolean success = MessageDatabase.markAsGloballyDeleted(messageId);
|
||||
if (success) {
|
||||
response = new ResponseModel("success", "Message deleted globally.");
|
||||
Message updated = MessageDatabase.findById(messageId);
|
||||
List<UUID> receivers = Receivers.resolveFor(updated.getReceiver_type(), updated.getReceiver_id(), null);
|
||||
RealTimeEventDispatcher.notifyMessageDeletedGlobal(updated.getReceiver_id(), messageId, receivers);
|
||||
|
||||
}
|
||||
else{
|
||||
response = new ResponseModel("error", "Failed to delete message globally.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
response = new ResponseModel("error", "Invalid delete_type.");
|
||||
break;
|
||||
}
|
||||
|
||||
case "edit_message" : {
|
||||
UUID msgId = UUID.fromString(requestJson.getString("message_id"));
|
||||
String newContent = requestJson.getString("new_content");
|
||||
|
||||
Message msg = MessageDatabase.findById(msgId);
|
||||
if (msg == null) {
|
||||
response = new ResponseModel("error", "Message not found.");
|
||||
break;
|
||||
}
|
||||
|
||||
//Only sender can edit
|
||||
if (!msg.getSender_id().equals(currentUser.getInternal_uuid())) {
|
||||
response = new ResponseModel("error", "You can only edit your own messages.");
|
||||
break;
|
||||
}
|
||||
|
||||
boolean success = MessageDatabase.updateContentAndMarkEdited(msgId, newContent);
|
||||
if (!success) {
|
||||
response = new ResponseModel("error", "Failed to update message.");
|
||||
} else {
|
||||
response = new ResponseModel("success", "Message edited.");
|
||||
Message updated = MessageDatabase.findById(msgId);
|
||||
List<UUID> receivers = Receivers.resolveFor(updated.getReceiver_type(), updated.getReceiver_id(), null);
|
||||
RealTimeEventDispatcher.notifyMessageEdited(updated.getReceiver_id(), updated.getMessage_id(), newContent, LocalDateTime.now(), receivers);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "send_reply_message" : {
|
||||
UUID senderId = currentUser.getInternal_uuid();
|
||||
String content = requestJson.getString("content");
|
||||
String receiverType = requestJson.getString("receiver_type");
|
||||
UUID receiverId = UUID.fromString(requestJson.getString("receiver_id"));
|
||||
//For block
|
||||
if ("private".equals(receiverType) && !canSendToPrivate(receiverId, senderId)) {
|
||||
response = new ResponseModel("error", "You can't message this user (blocked).");
|
||||
break;
|
||||
}
|
||||
|
||||
UUID replyToId = UUID.fromString(requestJson.getString("reply_to_id"));
|
||||
|
||||
Message message = new Message(
|
||||
UUID.randomUUID(),
|
||||
senderId,
|
||||
receiverType,
|
||||
receiverId,
|
||||
content,
|
||||
"TEXT",
|
||||
LocalDateTime.now(),
|
||||
"SEND",
|
||||
replyToId
|
||||
);
|
||||
|
||||
boolean saved = MessageDatabase.saveReplyMessage(message);
|
||||
|
||||
String excerpt = MessageDatabase.getExcerpt(replyToId);
|
||||
JSONObject meta = new JSONObject().put("reply_to", new JSONObject()
|
||||
.put("id", replyToId.toString())
|
||||
.put("excerpt", excerpt));
|
||||
|
||||
List<UUID> receivers = Receivers.resolveFor(receiverType, receiverId, senderId);
|
||||
//RealTimeEventDispatcher.sendNewMessage(message, receivers, "reply", meta);
|
||||
RealTimeEventDispatcher.sendNewMessageFiltered(message, receivers, senderId, "reply", meta);
|
||||
|
||||
|
||||
response = saved ?
|
||||
new ResponseModel("success", "Reply sent") :
|
||||
new ResponseModel("error", "Failed to send reply");
|
||||
break;
|
||||
}
|
||||
|
||||
case "forward_message" : {
|
||||
UUID senderId = currentUser.getInternal_uuid();
|
||||
UUID originalMessageId = UUID.fromString(requestJson.getString("original_message_id"));
|
||||
UUID targetChatId = UUID.fromString(requestJson.getString("target_chat_id"));
|
||||
String targetChatType = requestJson.getString("target_chat_type");
|
||||
|
||||
//For block
|
||||
if ("private".equals(targetChatType) && !canSendToPrivate(targetChatId, senderId)) {
|
||||
response = new ResponseModel("error", "You can't message this user (blocked).");
|
||||
break;
|
||||
}
|
||||
|
||||
// original message
|
||||
Message original = MessageDatabase.findById(originalMessageId);
|
||||
if (original == null) {
|
||||
response = new ResponseModel("error", "Original message not found.");
|
||||
break;
|
||||
}
|
||||
|
||||
//make forwarded message
|
||||
Message forwarded = new Message(
|
||||
UUID.randomUUID(),
|
||||
currentUser.getInternal_uuid(),
|
||||
targetChatType,
|
||||
targetChatId,
|
||||
original.getContent(),
|
||||
original.getMessage_type(),
|
||||
LocalDateTime.now(),
|
||||
"SEND",
|
||||
null, // reply_to_id
|
||||
false, // is_edited
|
||||
false, // is_deleted_globally
|
||||
original.getMessage_id(), //original message id
|
||||
currentUser.getInternal_uuid(), // forwarded_by
|
||||
original.getSender_id(), // forwarded_from
|
||||
null // edited_at
|
||||
);
|
||||
|
||||
boolean success = MessageDatabase.saveForwardedMessage(forwarded);
|
||||
|
||||
if (success) {
|
||||
response = new ResponseModel("success", "Message forwarded.");
|
||||
JSONObject meta = new JSONObject().put("forwarded_from", new JSONObject()
|
||||
.put("chat_id", original.getReceiver_id().toString())
|
||||
.put("message_id", original.getMessage_id().toString())
|
||||
.put("sender_id", original.getSender_id().toString())
|
||||
.put("sender_name", userDatabase.findByInternalUUID(original.getSender_id()).getProfile_name()));
|
||||
|
||||
List<UUID> receivers = Receivers.resolveFor(targetChatType, targetChatId, currentUser.getInternal_uuid());
|
||||
//RealTimeEventDispatcher.sendNewMessage(forwarded, receivers, "forward", meta);
|
||||
RealTimeEventDispatcher.sendNewMessageFiltered(forwarded, receivers, senderId, "forward", meta);
|
||||
|
||||
} else {
|
||||
response = new ResponseModel("error", "Failed to forward message.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "react_to_message": {
|
||||
UUID messageId = UUID.fromString(requestJson.getString("message_id"));
|
||||
String reaction = requestJson.getString("reaction");
|
||||
boolean success = MessageReactionDatabase.saveOrUpdateReaction(messageId, currentUser.getInternal_uuid(), reaction);
|
||||
|
||||
|
||||
if (success) {
|
||||
Message msg = MessageDatabase.findById(messageId);
|
||||
|
||||
JSONObject counts = MessageReactionDatabase.getCountsAsJson(messageId);
|
||||
|
||||
List<UUID> receivers = Receivers.resolveFor(msg.getReceiver_type(), msg.getReceiver_id(), null);
|
||||
|
||||
RealTimeEventDispatcher.notifyReactionAdded(
|
||||
msg.getReceiver_id(), // chatId
|
||||
messageId, // messageId
|
||||
reaction, // emoji
|
||||
counts.optInt(reaction, 0),
|
||||
counts,
|
||||
receivers
|
||||
);
|
||||
|
||||
response = new ResponseModel("success", "Reaction saved.");
|
||||
} else {
|
||||
response = new ResponseModel("error", "Failed to save reaction.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_user_profile": {
|
||||
// Get the current user's UUID
|
||||
UUID user_UUID = this.currentUser.getInternal_uuid();
|
||||
@@ -2122,8 +2502,8 @@ public class ClientHandler implements Runnable {
|
||||
}
|
||||
|
||||
case "send_saved_messages": {
|
||||
response = SidebarService.handleSendMessage(requestJson);
|
||||
break;
|
||||
response = SidebarService.handleSendMessage(requestJson);
|
||||
break;
|
||||
}
|
||||
|
||||
case "search_contacts": {
|
||||
@@ -2213,6 +2593,13 @@ public class ClientHandler implements Runnable {
|
||||
|
||||
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).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2320,6 +2707,14 @@ public class ClientHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
private boolean canSendToPrivate(UUID chatId, UUID senderId) {
|
||||
UUID other = PrivateChatDatabase.getOtherParticipant(chatId, senderId);
|
||||
if (other == null) return false;
|
||||
return !(ContactDatabase.isBlocked(senderId, other) || ContactDatabase.isBlocked(other, senderId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.ChannelDatabase;
|
||||
import org.to.telegramfinalproject.Database.ContactDatabase;
|
||||
import org.to.telegramfinalproject.Database.GroupDatabase;
|
||||
import org.to.telegramfinalproject.Database.userDatabase;
|
||||
import org.to.telegramfinalproject.Models.Message;
|
||||
@@ -81,33 +82,119 @@ public class RealTimeEventDispatcher {
|
||||
|
||||
|
||||
|
||||
public static void notifyMessageEdited(UUID messageId, String newContent, List<UUID> receivers) {
|
||||
|
||||
String editTime = LocalDateTime.now().toString();
|
||||
public static void notifyMessageEdited(UUID chatId, UUID messageId, String newContent, LocalDateTime editedAt, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("new_content", newContent);
|
||||
data.put("edited_at", editTime);
|
||||
data.put("edited_at", editedAt.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "edit_message");
|
||||
event.put("action", "message_edited");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyMessageDeleted(UUID messageId, List<UUID> receivers) {
|
||||
|
||||
public static void sendNewMessage(Message message, List<UUID> receivers, String kind, JSONObject meta) {
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("action", "new_message");
|
||||
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("id", message.getMessage_id().toString());
|
||||
data.put("chat_id", message.getReceiver_id().toString());
|
||||
data.put("sender_id", message.getSender_id().toString());
|
||||
data.put("receiver_id", message.getReceiver_id().toString());
|
||||
data.put("receiver_type", message.getReceiver_type());
|
||||
data.put("content", message.getContent());
|
||||
data.put("message_type", message.getMessage_type());
|
||||
data.put("send_at", message.getSend_at().toString());
|
||||
|
||||
User sender = userDatabase.findByInternalUUID(message.getSender_id());
|
||||
if (sender != null) data.put("sender_name", sender.getProfile_name());
|
||||
|
||||
data.put("kind", kind == null ? "plain" : kind); // plain|reply|forward
|
||||
if (meta != null) data.put("meta", meta); // reply_to {...} | forwarded_from {...}
|
||||
|
||||
payload.put("data", data);
|
||||
for (UUID userId : receivers) sendToUser(userId, payload);
|
||||
}
|
||||
|
||||
public static void sendNewMessageFiltered(Message m, List<UUID> receivers, UUID senderId, String kind, JSONObject meta) {
|
||||
JSONObject payload = new JSONObject()
|
||||
.put("action", "new_message")
|
||||
.put("data", new JSONObject()
|
||||
.put("id", m.getMessage_id().toString())
|
||||
.put("message_id", m.getMessage_id().toString())
|
||||
.put("sender_id", m.getSender_id().toString())
|
||||
.put("sender_name", userDatabase.findByInternalUUID(m.getSender_id()).getProfile_name())
|
||||
.put("receiver_id", m.getReceiver_id().toString())
|
||||
.put("receiver_type", m.getReceiver_type())
|
||||
.put("content", m.getContent())
|
||||
.put("message_type", m.getMessage_type())
|
||||
.put("send_at", m.getSend_at().toString())
|
||||
.put("kind", kind)
|
||||
.put("meta", meta != null ? meta : JSONObject.NULL)
|
||||
);
|
||||
|
||||
for (UUID uid : receivers) {
|
||||
//If receiver blocked
|
||||
if ("private".equalsIgnoreCase(m.getReceiver_type()) && ContactDatabase.isBlocked(uid, senderId)) continue;
|
||||
sendToUser(uid, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void notifyMessageDeletedGlobal(UUID chatId, UUID messageId, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "delete_message");
|
||||
event.put("action", "message_deleted_global");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
public static void notifyReactionAdded(UUID chatId, UUID messageId, String emoji,
|
||||
int totalForEmoji, JSONObject countsAll, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("emoji", emoji);
|
||||
data.put("counts", countsAll); // {"❤️":3,"👍":1,...}
|
||||
data.put("count_for_emoji", totalForEmoji);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_reacted");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
public static void notifyReactionRemoved(UUID chatId, UUID messageId, String emoji,
|
||||
int totalForEmoji, JSONObject countsAll, List<UUID> receivers) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("chat_id", chatId.toString());
|
||||
data.put("message_id", messageId.toString());
|
||||
data.put("emoji", emoji);
|
||||
data.put("counts", countsAll);
|
||||
data.put("count_for_emoji", totalForEmoji);
|
||||
|
||||
JSONObject event = new JSONObject();
|
||||
event.put("action", "message_unreacted");
|
||||
event.put("data", data);
|
||||
|
||||
broadcastToUsers(receivers, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static void notifyUserUpdated(UUID userId, String newProfileName, String newImageUrl, List<UUID> contactIds) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("user_id", userId.toString());
|
||||
@@ -328,6 +415,13 @@ public class RealTimeEventDispatcher {
|
||||
for (UUID userId : receivers) {
|
||||
sendToUser(userId, payload);
|
||||
}
|
||||
|
||||
|
||||
for (UUID uid : receivers) {
|
||||
//If receiver blocked
|
||||
if ("private".equalsIgnoreCase(message.getReceiver_type()) && ContactDatabase.isBlocked(uid, message.getSender_id())) continue;
|
||||
sendToUser(uid, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.to.telegramfinalproject.Server;
|
||||
|
||||
import org.to.telegramfinalproject.Database.ChannelDatabase;
|
||||
import org.to.telegramfinalproject.Database.GroupDatabase;
|
||||
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Receivers {
|
||||
public static List<UUID> resolveFor(String type, UUID chatId, UUID exclude) {
|
||||
List<UUID> ids = switch (type) {
|
||||
case "private" -> PrivateChatDatabase.getMembers(chatId);
|
||||
case "group" -> GroupDatabase.getMemberUUIDs(chatId);
|
||||
case "channel" -> ChannelDatabase.getSubscriberUUIDs(chatId);
|
||||
default -> List.of();
|
||||
};
|
||||
if (exclude != null) ids.remove(exclude);
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,12 @@ package org.to.telegramfinalproject.Utils;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.to.telegramfinalproject.Database.ChannelDatabase;
|
||||
import org.to.telegramfinalproject.Database.ConnectionDb;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChannelPermissionUtil {
|
||||
@@ -51,4 +56,21 @@ public class ChannelPermissionUtil {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean canDeleteMessage(UUID channelId, UUID userId) {
|
||||
String roleSql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
|
||||
try (Connection conn = ConnectionDb.connect();
|
||||
PreparedStatement ps = conn.prepareStatement(roleSql)) {
|
||||
ps.setObject(1, channelId);
|
||||
ps.setObject(2, userId);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next()) {
|
||||
String role = rs.getString("role");
|
||||
return role.equalsIgnoreCase("owner") || role.equalsIgnoreCase("admin");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS messages (
|
||||
content TEXT,
|
||||
message_type VARCHAR(20) DEFAULT 'TEXT' CHECK (message_type IN ('TEXT','IMAGE','FILE','VIDEO','AUDIO','STICKER','GIF')),
|
||||
send_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'SEND', -- SEND,DELIVERED,READ
|
||||
status VARCHAR(20) DEFAULT 'SEND', -- SEND,DELIVERED,READ
|
||||
reply_to_id UUID REFERENCES messages(message_id) ON DELETE SET NULL, --(Bouns)
|
||||
is_edited BOOLEAN DEFAULT FALSE, --(Bonus)
|
||||
edited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -145,7 +145,15 @@ CREATE TABLE IF NOT EXISTS message_attachments (
|
||||
message_id UUID REFERENCES messages(message_id) ON DELETE CASCADE,
|
||||
file_url TEXT NOT NULL,
|
||||
file_type VARCHAR(20) CHECK (file_type IN ('IMAGE','VIDEO','AUDIO','FILE','GIF','STICKER')),
|
||||
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
file_name TEXT,
|
||||
file_size BIGINT,
|
||||
mime_type TEXT,
|
||||
width INT,
|
||||
height INT,
|
||||
duration_seconds INT,
|
||||
thumbnail_url TEXT
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user