Compare commits
3 Commits
13800486ba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ee3291465 | |||
| b0e9f1bb4e | |||
| b8bc715868 |
@@ -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)
|
||||||
|
|||||||
94
README.md
94
README.md
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user