Compare commits

...

10 commits

Author SHA1 Message Date
654aca86cc misc: Update the docker-compose setup
To use the new build and configuration way.
2024-11-27 23:45:31 +01:00
034a5152df bot: Fix not performing the async processing
If I don't wait for the future the calls are not made.
Waiting for the response is kinda ugly though and blocks the
main thread.
This should be seen more as a quick fix.
Kinda WIP!
2024-11-27 23:43:01 +01:00
9fe5175f13 bot: Fix not using provided system message 2024-11-27 23:40:50 +01:00
7fe9a82176 discord-ws: Fix sequence counting
Do not reset on events like heartbeat acks which have
seq 0/null.
2024-11-27 23:39:53 +01:00
6e01af7572 openai-rest: Fix response deserialization
Didn't use the body -_-
2024-11-27 23:38:38 +01:00
9701d3faea discord-rest: Fix missing content-type header 2024-11-27 23:37:40 +01:00
79a13592c1 discord-rest: Add logging 2024-11-27 23:37:16 +01:00
db872beac5 discord-ws: Add logging 2024-11-27 23:36:47 +01:00
161509ad96 openai-rest: Add logging 2024-11-27 23:36:15 +01:00
ac14f7ad42 bot: Enable debug information in runtime
To have stacktraces with information about the code
line with the error.
2024-11-27 23:34:04 +01:00
11 changed files with 39 additions and 23 deletions

View file

@ -1,13 +0,0 @@
# No need to change
POSTGRES_DB=dchat
# "db" being the name of the postgresql service inside the docker-compose.yml, "5432" is the port on which postgres
# listens for new connections and "dchat" is the database name.
POSTGRES_URL=jdbc:postgresql://db:5432/dchat
# Please change
DISCORD_API_KEY=<discord-api-key>
OPENAI_API_KEY=<openai-api-key>
# Those values can not change after the first start
POSTGRES_USER=<postgres-user>
POSTGRES_PASSWORD=<postgres-password>

1
.gitignore vendored
View file

@ -126,5 +126,6 @@ fabric.properties
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
.env .env
dchat.properties
data/ data/

View file

@ -46,7 +46,6 @@
<!--Remove unneeded files and symbols to reduce the image size and performance--> <!--Remove unneeded files and symbols to reduce the image size and performance-->
<noHeaderFiles>true</noHeaderFiles> <noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages> <noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<!--Compression is not needed since we unzip it in our OCI build--> <!--Compression is not needed since we unzip it in our OCI build-->
<compress>zip-0</compress> <compress>zip-0</compress>
</configuration> </configuration>

View file

@ -10,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
public final class MessageService { public final class MessageService {
@ -27,12 +28,12 @@ public final class MessageService {
} }
public void process(final String messageContent, final String referencedMessageContent, final String guildId, final String channelId, final String originalMessageId) { public void process(final String messageContent, final String referencedMessageContent, final String guildId, final String channelId, final String originalMessageId) {
final var chatGPTRequest = new ChatGPTRequestBuilder().contextRequest(referencedMessageContent, messageContent, "system msg"); final var chatGPTRequest = new ChatGPTRequestBuilder().contextRequest(referencedMessageContent, messageContent, systemMessage);
process(chatGPTRequest, channelId, originalMessageId, guildId); process(chatGPTRequest, channelId, originalMessageId, guildId);
} }
public void process(final String messageContent, final String guildId, final String channelId, final String originalMessageId) { public void process(final String messageContent, final String guildId, final String channelId, final String originalMessageId) {
final var chatGPTRequest = new ChatGPTRequestBuilder().contextRequest(messageContent, "system msg"); final var chatGPTRequest = new ChatGPTRequestBuilder().contextRequest(messageContent, systemMessage);
process(chatGPTRequest, channelId, originalMessageId, guildId); process(chatGPTRequest, channelId, originalMessageId, guildId);
} }
@ -46,6 +47,11 @@ public final class MessageService {
} }
}; };
executorService.submit(callable); // FIXME: We should find a solution to not block the main thread without loosing the started thread.
try {
executorService.submit(callable).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
} }
} }

View file

@ -22,12 +22,15 @@ public final class DiscordRest {
} }
public void postMessage(final String channelId, final Message message) throws IOException, InterruptedException { public void postMessage(final String channelId, final Message message) throws IOException, InterruptedException {
final String data = message.toJson();
logger.trace("Submitting new discord message: {}", data);
final var uri = URI.create(DISCORD_URL + "/channels/%s/messages".formatted(channelId)); final var uri = URI.create(DISCORD_URL + "/channels/%s/messages".formatted(channelId));
final HttpRequest request = HttpRequest.newBuilder() final HttpRequest request = HttpRequest.newBuilder()
.uri(uri) .uri(uri)
.header("Authorization", "Bot " + discordApiKey) .header("Authorization", "Bot " + discordApiKey)
.header("User-Agent", USER_AGENT) .header("User-Agent", USER_AGENT)
.POST(HttpRequest.BodyPublishers.ofString(message.toJson())) .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(data))
.build(); .build();
final HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); final HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding());

View file

@ -1,8 +1,12 @@
package de.hhhammer.dchat.discord.ws.connection; package de.hhhammer.dchat.discord.ws.connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.http.WebSocket; import java.net.http.WebSocket;
public final class ResumeConnectionInitiator implements ConnectionInitiator { public final class ResumeConnectionInitiator implements ConnectionInitiator {
private static final Logger logger = LoggerFactory.getLogger(ResumeConnectionInitiator.class);
private final String token; private final String token;
private final String sessionId; private final String sessionId;
private final int lastSequence; private final int lastSequence;
@ -16,6 +20,7 @@ public final class ResumeConnectionInitiator implements ConnectionInitiator {
@Override @Override
public void initiate(final WebSocket webSocket) { public void initiate(final WebSocket webSocket) {
final String identifyPayload = "{\"op\": 6, \"d\": {\"token\": \"" + token + "\", \"session_id\": \"" + sessionId + "\", \"seq\": " + lastSequence + "}}"; final String identifyPayload = "{\"op\": 6, \"d\": {\"token\": \"" + token + "\", \"session_id\": \"" + sessionId + "\", \"seq\": " + lastSequence + "}}";
logger.debug("Resuming connection");
webSocket.sendText(identifyPayload, true); webSocket.sendText(identifyPayload, true);
} }
} }

View file

@ -68,7 +68,7 @@ public final class DiscordListener implements WebSocket.Listener {
final String text = optText.get(); final String text = optText.get();
final Event event = eventDeserializer.deserialize(text); final Event event = eventDeserializer.deserialize(text);
final int currentSequence = event.sequence(); final int currentSequence = event.sequence();
lastSeq.set(currentSequence); if (currentSequence != 0) lastSeq.set(currentSequence);
if (!(event.type() instanceof EventType.Empty)) { if (!(event.type() instanceof EventType.Empty)) {
if (event.type() instanceof EventType.Ready) { if (event.type() instanceof EventType.Ready) {
final JSONObject payload = event.data(); final JSONObject payload = event.data();
@ -109,6 +109,7 @@ public final class DiscordListener implements WebSocket.Listener {
@Override @Override
public CompletionStage<?> onClose(final WebSocket webSocket, final int statusCode, final String reason) { public CompletionStage<?> onClose(final WebSocket webSocket, final int statusCode, final String reason) {
logger.info("Connection closed: {}: {}", statusCode, reason);
switch (statusCode) { switch (statusCode) {
case 0, 4000, 4001, 4002, 4005, 4008 -> case 0, 4000, 4001, 4002, 4005, 4008 ->
this.closeEventQueue.add(new CloseEvent.ResumableCloseEvent(this.resumeGatewayUrl.get(), this.sessionId.get(), lastSeq.get())); this.closeEventQueue.add(new CloseEvent.ResumableCloseEvent(this.resumeGatewayUrl.get(), this.sessionId.get(), lastSeq.get()));
@ -141,6 +142,7 @@ public final class DiscordListener implements WebSocket.Listener {
final int intSeq = lastSeq.get(); final int intSeq = lastSeq.get();
final String stringSeq = intSeq != 0 ? String.valueOf(intSeq) : "null"; final String stringSeq = intSeq != 0 ? String.valueOf(intSeq) : "null";
// Send heartbeat // Send heartbeat
logger.debug("Sending heartbeat: {}",stringSeq);
webSocket.sendText("{\"op\": 1, \"d\": %s}".formatted(stringSeq), true); webSocket.sendText("{\"op\": 1, \"d\": %s}".formatted(stringSeq), true);
receivedAck.set(false); receivedAck.set(false);
TimeUnit.MILLISECONDS.sleep(heartbeatInterval); TimeUnit.MILLISECONDS.sleep(heartbeatInterval);

View file

@ -1,10 +1,11 @@
services: services:
bot: bot:
image: git.hhhammer.de/hamburghammer/dchat/bot:latest image: git.hhhammer.de/hamburghammer/dchat/bot:latest
env_file:
- .env
build: build:
dockerfile: Dockerfile dockerfile: Dockerfile
context: . context: .
target: bot
restart: unless-stopped restart: unless-stopped
environment:
DCHAT_CONFIG_FILE: "/opt/dchat/dchat.properties"
volumes:
- ./dchat.properties:/opt/dchat/dchat.properties:ro

7
example.properties Normal file
View file

@ -0,0 +1,7 @@
dchat.discord-api-key=xxx
dchat.openai-api-key=xxx
dchat.bot-id=xxx
# comma separated list
dchat.allowed-guild-ids=foo,bar
# not allowed to contain "!
dchat.system-message=

View file

@ -2,6 +2,8 @@ package de.hhhammer.dchat.openai.rest;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -11,6 +13,7 @@ import java.net.http.HttpResponse;
import java.time.Duration; import java.time.Duration;
public final class ChatGPTService { public final class ChatGPTService {
private static final Logger logger = LoggerFactory.getLogger(ChatGPTService.class);
private final String apiKey; private final String apiKey;
private final HttpClient httpClient; private final HttpClient httpClient;
@ -21,6 +24,7 @@ public final class ChatGPTService {
public String submit(final ChatGPTRequest chatGPTRequest) throws IOException, InterruptedException, ResponseException { public String submit(final ChatGPTRequest chatGPTRequest) throws IOException, InterruptedException, ResponseException {
final String data = chatGPTRequest.toJson().toString(); final String data = chatGPTRequest.toJson().toString();
logger.trace("Submitting new ChatGPT request: {}", data);
final URI uri = URI.create("https://api.openai.com/v1/chat/completions"); final URI uri = URI.create("https://api.openai.com/v1/chat/completions");
final HttpRequest request = HttpRequest.newBuilder(uri) final HttpRequest request = HttpRequest.newBuilder(uri)
.POST(HttpRequest.BodyPublishers.ofString(data)) .POST(HttpRequest.BodyPublishers.ofString(data))
@ -34,7 +38,7 @@ public final class ChatGPTService {
throw new ResponseException("Response status code was not 200: " + response.statusCode()); throw new ResponseException("Response status code was not 200: " + response.statusCode());
} }
final var responseJson = new JSONObject(response); final var responseJson = new JSONObject(response.body());
final JSONArray choices = responseJson.getJSONArray("choices"); final JSONArray choices = responseJson.getJSONArray("choices");
return getResponse(choices); return getResponse(choices);
} }

View file

@ -5,4 +5,5 @@ module de.hhhammer.dchat.openai.rest {
requires org.json; requires org.json;
requires static org.jetbrains.annotations; requires static org.jetbrains.annotations;
requires org.slf4j;
} }