Compare commits

...

5 commits

9 changed files with 119 additions and 59 deletions

13
.env.example Normal file
View file

@ -0,0 +1,13 @@
# 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

@ -125,3 +125,4 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
.env

View file

@ -7,28 +7,67 @@ A ChatGPT Bot for Discord.
Requirements:
- [git](https://git-scm.com/)
- [Podman](https://podman.io/) or [Docker](https://www.docker.com/)
- [Docker](https://www.docker.com/)
- [OpenAI API Key](https://platform.openai.com/account/api-keys)
- [Discord Bot Token](https://javacord.org/wiki/getting-started/creating-a-bot-account.html)
Clone the project:
### Clone the project
```shell
git clone https://git.hhhammer.de/hamburghammer/dchat.git
cd dchat
```
and navigate into it.
### Obtain the images
Change the environment variable inside the `docker-compose.yml`.
#### Build the images
```shell
podman-compose up -d
docker compose build
```
#### Pull the prebuilt images
```shell
docker compose pull
```
### Configure environment
```shell
cp .env.example .env
```
Fill the required variables.
### Start
For the fist time we want to start the containers in the following order:
First crate the DB.
```shell
docker compose up -d db
```
Crate the required tables and migrate already existing data.
```shell
docker compose up -d migration
```
Start the final apps.
```shell
docker compose up -d
```
### Invite
Invite the bot through the link provided in the container logs.
```shell
podman container logs dchat_db_1
docker compose logs bot
```
## LICENSE

View file

@ -1,10 +1,17 @@
package de.hhhammer.dchat.bot.discord;
import de.hhhammer.dchat.bot.openai.ResponseException;
import org.javacord.api.entity.message.MessageType;
import org.javacord.api.event.message.MessageCreateEvent;
import org.javacord.api.listener.message.MessageCreateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MessageCreateHandler implements MessageCreateListener {
private static final Logger logger = LoggerFactory.getLogger(MessageCreateHandler.class);
private final MessageHandler messageHandler;
public MessageCreateHandler(MessageHandler messageHandler) {
@ -27,7 +34,12 @@ public class MessageCreateHandler implements MessageCreateListener {
event.getChannel().sendMessage("Rate limit hit - cooling down...");
return;
}
try {
this.messageHandler.handle(event);
} catch (ResponseException | IOException | InterruptedException e) {
logger.error("Reading a message from the listener", e);
event.getMessage().reply("Sorry but something went wrong :(");
}
});
}
}

View file

@ -1,9 +1,12 @@
package de.hhhammer.dchat.bot.discord;
import de.hhhammer.dchat.bot.openai.ResponseException;
import org.javacord.api.event.message.MessageCreateEvent;
import java.io.IOException;
public interface MessageHandler {
void handle(MessageCreateEvent event);
void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException;
boolean isAllowed(MessageCreateEvent event);

View file

@ -27,7 +27,7 @@ public class ServerMessageHandler implements MessageHandler {
}
@Override
public void handle(MessageCreateEvent event) {
public void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException {
String content = extractContent(event);
var serverId = event.getServer().get().getId();
var systemMessage = this.serverDBService.getConfig(String.valueOf(serverId)).get().systemMessage();
@ -41,19 +41,14 @@ public class ServerMessageHandler implements MessageHandler {
.stream().toList(),
content, systemMessage) :
new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
try {
var response = this.chatGPTService.submit(request);
if (response.choices().size() < 1) {
event.getChannel().sendMessage("No response available");
event.getMessage().reply("No response available");
return;
}
var answer = response.choices().get(0).message().content();
logServerMessage(event, response.usage().totalTokens());
event.getMessage().reply(answer);
} catch (IOException | InterruptedException | ResponseException e) {
logger.error("Reading a message from the listener", e);
event.getChannel().sendMessage("Sorry but something went wrong :(");
}
}
@Override

View file

@ -1,10 +1,10 @@
package de.hhhammer.dchat.bot.discord;
import de.hhhammer.dchat.db.UserDBService;
import de.hhhammer.dchat.db.models.user.UserMessage;
import de.hhhammer.dchat.bot.openai.ChatGPTRequestBuilder;
import de.hhhammer.dchat.bot.openai.ChatGPTService;
import de.hhhammer.dchat.bot.openai.ResponseException;
import de.hhhammer.dchat.db.UserDBService;
import de.hhhammer.dchat.db.models.user.UserMessage;
import org.javacord.api.event.message.MessageCreateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,24 +22,19 @@ public class UserMessageHandler implements MessageHandler {
}
@Override
public void handle(MessageCreateEvent event) {
public void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException {
String content = event.getReadableMessageContent();
var userId = event.getMessageAuthor().getId();
var systemMessage = this.userDBService.getConfig(String.valueOf(userId)).get().systemMessage();
var request = new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
try {
var response = this.chatGPTService.submit(request);
if (response.choices().size() < 1) {
event.getChannel().sendMessage("No response available");
event.getMessage().reply("No response available");
return;
}
var answer = response.choices().get(0).message().content();
logUserMessage(event, content, answer, response.usage().totalTokens());
event.getMessage().reply(answer);
} catch (IOException | InterruptedException | ResponseException e) {
logger.error("Reading a message from the listener", e);
event.getChannel().sendMessage("Sorry but something went wrong :(");
}
}
@Override

View file

@ -12,7 +12,7 @@ import java.net.http.HttpResponse;
import java.time.Duration;
public class ChatGPTService {
private final String url = "https://api.openai.com/v1/chat/completions";
private static final String url = "https://api.openai.com/v1/chat/completions";
private final ObjectMapper mapper = new ObjectMapper();
private final String apiKey;
private final HttpClient httpClient;
@ -29,15 +29,14 @@ public class ChatGPTService {
.POST(HttpRequest.BodyPublishers.ofByteArray(data))
.setHeader("Content-Type", "application/json")
.setHeader("Authorization", "Bearer " + this.apiKey)
.timeout(Duration.ofSeconds(30))
.timeout(Duration.ofSeconds(90))
.build();
var responseStream = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
if (responseStream.statusCode() != 200) {
throw new ResponseException("Response status code was not 200: " + responseStream.statusCode());
}
ChatGPTResponse response = mapper.readValue(responseStream.body(), ChatGPTResponse.class);
return response;
return mapper.readValue(responseStream.body(), ChatGPTResponse.class);
}
}

View file

@ -3,46 +3,49 @@ version: "3"
services:
bot:
image: git.hhhammer.de/hamburghammer/dchat/bot:latest
build: ./bot/
build:
dockerfile: Containerfile
context: .
target: bot
restart: unless-stopped
depends_on:
- db
- migration
environment:
- DISCORD_API_KEY=<discord-api-key>
- OPENAI_API_KEY=<openai-api-key>
- POSTGRES_USER=<postgres-user>
- POSTGRES_PASSWORD=<postgres-password>
- POSTGRES_URL=jdbc:postgresql://db:5432/dchat
- JDK_JAVA_OPTIONS="--enable-preview"
web:
image: git.hhhammer.de/hamburghammer/dchat/web:latest
build: ./web/
build:
dockerfile: Containerfile
context: .
target: web
restart: unless-stopped
depends_on:
- db
- migration
ports:
- 8080:8080
environment:
- POSTGRES_USER=<postgres-user>
- POSTGRES_PASSWORD=<postgres-password>
- POSTGRES_URL=jdbc:postgresql://db:5432/dchat
- JDK_JAVA_OPTIONS="--enable-preview"
migration:
image: git.hhhammer.de/hamburghammer/dchat/migration:latest
build: ./migration/
build:
dockerfile: Containerfile
context: .
target: migration
depends_on:
- db
environment:
- POSTGRES_USER=<postgres-user>
- POSTGRES_PASSWORD=<postgres-password>
- POSTGRES_URL=jdbc:postgresql://db:5432/dchat
db:
image: docker.io/postgres:15-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=<postgres-user>
- POSTGRES_PASSWORD=<postgres-password>
- POSTGRES_DB=dchat
volumes:
- ./data/postgres:/var/lib/postgresql/data:rw
healthcheck:
test: [ "CMD-SHELL", "pg_isready", "-d", $POSTGRES_DB ]
interval: 10s
timeout: 5s
retries: 5
start_period: 60s