Compare commits
10 commits
2ab3c5e01d
...
654aca86cc
Author | SHA1 | Date | |
---|---|---|---|
654aca86cc | |||
034a5152df | |||
9fe5175f13 | |||
7fe9a82176 | |||
6e01af7572 | |||
9701d3faea | |||
79a13592c1 | |||
db872beac5 | |||
161509ad96 | |||
ac14f7ad42 |
11 changed files with 39 additions and 23 deletions
13
.env.example
13
.env.example
|
@ -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
1
.gitignore
vendored
|
@ -126,5 +126,6 @@ fabric.properties
|
||||||
.idea/caches/build_file_checksums.ser
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
dchat.properties
|
||||||
|
|
||||||
data/
|
data/
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
7
example.properties
Normal 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=
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue