Compare commits

...

3 Commits

Author SHA1 Message Date
9ee3291465 feat: better readme 2025-04-05 13:56:38 +02:00
b0e9f1bb4e feat: public chat is now emote-only 2025-04-05 01:00:59 +02:00
b8bc715868 feat: add public message 2025-04-05 00:14:18 +02:00
5 changed files with 148 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
arguments=--init-script /home/xeon0x/.var/app/dev.zed.Zed/data/zed/extensions/work/java/jdtls/jdt-language-server-1.46.0-202503271314/configuration/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle arguments=--init-script /home/xeon0x/.var/app/dev.zed.Zed/data/zed/extensions/work/java/jdtls/jdt-language-server-1.46.1-202504011455/configuration/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle
auto.sync=false auto.sync=false
build.scans.enabled=false build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)

View File

@@ -1,5 +1,24 @@
# ClientServer # ClientServer
A Java-based ☕️↔️ UDP client-server chat 💬 system with automatic 🔄 text-to-emoji 😊 conversion using Mistral API.
## Features
### Available Commands
- `/list` : Display connected clients list
- `/msg <nickname> <message>` : Send private message to specific client
- `/help` : Display help with all available commands
- `/disconnect` : Clean server disconnection
- `<message>` : Broadcast message translated to emojis to all connected clients
### Technical Features
- UDP Communication
- Multi-threading for client handling
- Mistral API for text-to-emoji conversion
- Disconnection handling
## Build ## Build
```bash ```bash
@@ -11,33 +30,76 @@
### Client ### Client
```bash ```bash
./gradlew run --args='client' ./gradlew runClient
``` ```
or The client will ask for a pseudo, an IP (type `localhost` if server and clients are on the same machine), and a port (type `6666` by default).
```bash
./gradlew runServer
```
### Server ### Server
```bash
./gradlew run --args='server'
```
or
```bash ```bash
./gradlew runServer ./gradlew runServer
``` ```
## Configuration
Create a `config.properties` file at project root (`ClientServer/app/config.properties`) with:
```properties
mistral.api.key=your_api_key
```
Replace `your_api_key` with your actual Mistral API key. Create one for free at https://console.mistral.ai/api-keys. If no API key is provided, the server will not be able to convert broadcasted messages to emojis and empty broadcasted messages will be sent.
## Documentation ## Documentation
### Sequence Diagram
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Note over Client, Serveur: DatagramSocket("localhost", 66666) participant MainServer
Client->>+Serveur: DatagramPacket("Salve !", 7, "localhost", 6666)
Note over Serveur, Client: DatagramSocket("localhost", 66666) participant Alice
Serveur->>+Client: DatagramPacket("Accusé de réception", 19, "localhost", 6666) Note over Alice: Creates DatagramSocket
Alice->>+MainServer: Sends username "Alice"
Note over MainServer: Creates new socket<br/>for AliceHandler
create participant AliceHandler
MainServer->>AliceHandler: Create new handler thread
MainServer->>-Alice: PORT:{AlicePort}
participant Bob
Bob->>+MainServer: Sends username "Bob"
Note over MainServer: Creates new socket<br/>for BobHandler
create participant BobHandler
MainServer->>BobHandler: Create new handler thread
MainServer->>-Bob: PORT:{BobPort}
rect rgb(200, 200, 255)
Note over Alice,Bob: Chat loop
Alice->>+AliceHandler: "Hi everyone!"
Note over AliceHandler: Converts to emojis
AliceHandler->>+BobHandler: Broadcast message
BobHandler->>-Bob: "Alice: 👋👩‍👩🌍🎉"
AliceHandler->>-Alice: Message sent
Bob->>+BobHandler: "Hello Alice, how are you?"
Note over BobHandler: Converts to emojis
BobHandler->>+AliceHandler: Broadcast message
AliceHandler->>-Alice: "Bob: 👋🏻👩🏼‍🦰🐰🤔👩🏼‍🦰🐇💬👩🏼‍🦰🐰👍🏻😊👩🏼‍🦰🐇👋🏻"
BobHandler->>-Bob: Message sent
end
Alice->>+AliceHandler: /disconnect
Note over AliceHandler: Removes Alice<br/>from map
AliceHandler->>-Alice: Disconnection confirmation
AliceHandler->>MainServer: Thread terminated
MainServer-->>AliceHandler: destroy
Bob->>+BobHandler: /disconnect
Note over BobHandler: Removes Bob<br/>from map
BobHandler->>-Bob: Disconnection confirmation
BobHandler->>MainServer: Thread terminated
MainServer-->>BobHandler: destroy
Note over Alice,Bob: Close sockets
``` ```

View File

@@ -10,10 +10,9 @@ import java.util.Properties;
public class MistralDirectAPI { public class MistralDirectAPI {
public static void main(String[] args) { private static String apiKey;
String apiKey;
// Load API key from properties file static {
try { try {
Properties props = new Properties(); Properties props = new Properties();
FileInputStream input = new FileInputStream("config.properties"); FileInputStream input = new FileInputStream("config.properties");
@@ -22,18 +21,22 @@ public class MistralDirectAPI {
input.close(); input.close();
} catch (IOException e) { } catch (IOException e) {
System.err.println("Could not load API key: " + e.getMessage()); System.err.println("Could not load API key: " + e.getMessage());
return; }
} }
String payload = public static String translateToEmojis(String message) {
String payload = String.format(
""" """
{ {
"model": "mistral-medium", "model": "mistral-medium",
"messages": [ "messages": [
{ "role": "user", "content": "Reverse turing test." } { "role": "system", "content": "Please translate the following message to emojis only. Do not include any comments. End the emoji message with ;" },
{ "role": "user", "content": "%s" }
] ]
} }
"""; """,
message
);
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.mistral.ai/v1/chat/completions")) .uri(URI.create("https://api.mistral.ai/v1/chat/completions"))
@@ -49,10 +52,26 @@ public class MistralDirectAPI {
request, request,
HttpResponse.BodyHandlers.ofString() HttpResponse.BodyHandlers.ofString()
); );
System.out.println("Response Code: " + response.statusCode()); String responseBody = response.body();
System.out.println("Response Body: " + response.body()); return extractContent(responseBody);
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
System.err.println("Error occurred: " + e.getMessage()); System.err.println("Error occurred: " + e.getMessage());
return message;
} }
} }
private static String extractContent(String jsonResponse) {
// Find the content field in the response
int contentIndex = jsonResponse.indexOf("\"content\":\"");
if (contentIndex != -1) {
int startIndex = contentIndex + "\"content\":\"".length();
int endIndex = jsonResponse.indexOf(";", startIndex);
if (endIndex != -1) {
return jsonResponse.substring(startIndex, endIndex);
}
}
return "";
}
} }

View File

@@ -1,6 +1,7 @@
package clientserver.server; package clientserver.server;
import clientserver.client.Client; import clientserver.client.Client;
import clientserver.common.MistralDirectAPI;
import java.net.DatagramSocket; import java.net.DatagramSocket;
public class MessageProcessor { public class MessageProcessor {
@@ -42,6 +43,10 @@ public class MessageProcessor {
case "/disconnect": case "/disconnect":
handleDisconnect(); handleDisconnect();
break; break;
default:
// Broadcast message to all clients
broadcastMessageEmoji(message);
break;
} }
} }
@@ -95,6 +100,37 @@ public class MessageProcessor {
); );
} }
private void broadcastMessage(String message) {
String formattedMessage = "> " + client.getPseudo() + ": " + message;
for (ClientHandler handler : Server.getAllClientHandlers()) {
Client targetClient = handler.getClient();
Server.sendMessage(
clientHandlerSocket,
formattedMessage,
targetClient.getAddress(),
targetClient.getPort()
);
}
}
private void broadcastMessageEmoji(String message) {
// Translate message to emojis
String emojiMessage = MistralDirectAPI.translateToEmojis(message);
String formattedMessage =
"> " + client.getPseudo() + ": " + emojiMessage;
for (ClientHandler handler : Server.getAllClientHandlers()) {
Client targetClient = handler.getClient();
Server.sendMessage(
clientHandlerSocket,
formattedMessage,
targetClient.getAddress(),
targetClient.getPort()
);
}
}
private void handleMsgCommand() { private void handleMsgCommand() {
Server.sendMessage( Server.sendMessage(
clientHandlerSocket, clientHandlerSocket,

View File

@@ -5,6 +5,7 @@ import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -179,6 +180,10 @@ public class Server {
return mapPseudosConnectedClientsHandlers.get(pseudo); return mapPseudosConnectedClientsHandlers.get(pseudo);
} }
public static Collection<ClientHandler> getAllClientHandlers() {
return mapPseudosConnectedClientsHandlers.values();
}
public void addConnectedClientHandler( public void addConnectedClientHandler(
String pseudo, String pseudo,
ClientHandler clientHandler ClientHandler clientHandler