Compare commits
5 commits
5662d2faf1
...
f5e40e324d
Author | SHA1 | Date | |
---|---|---|---|
f5e40e324d | |||
5459cf1dd1 | |||
3641b01162 | |||
f457c23301 | |||
92473716fb |
9 changed files with 119 additions and 59 deletions
13
.env.example
Normal file
13
.env.example
Normal 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
1
.gitignore
vendored
|
@ -125,3 +125,4 @@ fabric.properties
|
||||||
# Android studio 3.1+ serialized cache file
|
# Android studio 3.1+ serialized cache file
|
||||||
.idea/caches/build_file_checksums.ser
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
51
README.md
51
README.md
|
@ -7,28 +7,67 @@ A ChatGPT Bot for Discord.
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [git](https://git-scm.com/)
|
- [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)
|
- [OpenAI API Key](https://platform.openai.com/account/api-keys)
|
||||||
- [Discord Bot Token](https://javacord.org/wiki/getting-started/creating-a-bot-account.html)
|
- [Discord Bot Token](https://javacord.org/wiki/getting-started/creating-a-bot-account.html)
|
||||||
|
|
||||||
Clone the project:
|
### Clone the project
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://git.hhhammer.de/hamburghammer/dchat.git
|
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
|
```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.
|
Invite the bot through the link provided in the container logs.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
podman container logs dchat_db_1
|
docker compose logs bot
|
||||||
```
|
```
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package de.hhhammer.dchat.bot.discord;
|
package de.hhhammer.dchat.bot.discord;
|
||||||
|
|
||||||
|
import de.hhhammer.dchat.bot.openai.ResponseException;
|
||||||
import org.javacord.api.entity.message.MessageType;
|
import org.javacord.api.entity.message.MessageType;
|
||||||
import org.javacord.api.event.message.MessageCreateEvent;
|
import org.javacord.api.event.message.MessageCreateEvent;
|
||||||
import org.javacord.api.listener.message.MessageCreateListener;
|
import org.javacord.api.listener.message.MessageCreateListener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class MessageCreateHandler implements MessageCreateListener {
|
public class MessageCreateHandler implements MessageCreateListener {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MessageCreateHandler.class);
|
||||||
|
|
||||||
private final MessageHandler messageHandler;
|
private final MessageHandler messageHandler;
|
||||||
|
|
||||||
public MessageCreateHandler(MessageHandler messageHandler) {
|
public MessageCreateHandler(MessageHandler messageHandler) {
|
||||||
|
@ -27,7 +34,12 @@ public class MessageCreateHandler implements MessageCreateListener {
|
||||||
event.getChannel().sendMessage("Rate limit hit - cooling down...");
|
event.getChannel().sendMessage("Rate limit hit - cooling down...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
this.messageHandler.handle(event);
|
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 :(");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package de.hhhammer.dchat.bot.discord;
|
package de.hhhammer.dchat.bot.discord;
|
||||||
|
|
||||||
|
import de.hhhammer.dchat.bot.openai.ResponseException;
|
||||||
import org.javacord.api.event.message.MessageCreateEvent;
|
import org.javacord.api.event.message.MessageCreateEvent;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface MessageHandler {
|
public interface MessageHandler {
|
||||||
void handle(MessageCreateEvent event);
|
void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException;
|
||||||
|
|
||||||
boolean isAllowed(MessageCreateEvent event);
|
boolean isAllowed(MessageCreateEvent event);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class ServerMessageHandler implements MessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MessageCreateEvent event) {
|
public void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException {
|
||||||
String content = extractContent(event);
|
String content = extractContent(event);
|
||||||
var serverId = event.getServer().get().getId();
|
var serverId = event.getServer().get().getId();
|
||||||
var systemMessage = this.serverDBService.getConfig(String.valueOf(serverId)).get().systemMessage();
|
var systemMessage = this.serverDBService.getConfig(String.valueOf(serverId)).get().systemMessage();
|
||||||
|
@ -41,19 +41,14 @@ public class ServerMessageHandler implements MessageHandler {
|
||||||
.stream().toList(),
|
.stream().toList(),
|
||||||
content, systemMessage) :
|
content, systemMessage) :
|
||||||
new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
|
new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
|
||||||
try {
|
|
||||||
var response = this.chatGPTService.submit(request);
|
var response = this.chatGPTService.submit(request);
|
||||||
if (response.choices().size() < 1) {
|
if (response.choices().size() < 1) {
|
||||||
event.getChannel().sendMessage("No response available");
|
event.getMessage().reply("No response available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var answer = response.choices().get(0).message().content();
|
var answer = response.choices().get(0).message().content();
|
||||||
logServerMessage(event, response.usage().totalTokens());
|
logServerMessage(event, response.usage().totalTokens());
|
||||||
event.getMessage().reply(answer);
|
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
|
@Override
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package de.hhhammer.dchat.bot.discord;
|
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.ChatGPTRequestBuilder;
|
||||||
import de.hhhammer.dchat.bot.openai.ChatGPTService;
|
import de.hhhammer.dchat.bot.openai.ChatGPTService;
|
||||||
import de.hhhammer.dchat.bot.openai.ResponseException;
|
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.javacord.api.event.message.MessageCreateEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -22,24 +22,19 @@ public class UserMessageHandler implements MessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MessageCreateEvent event) {
|
public void handle(MessageCreateEvent event) throws ResponseException, IOException, InterruptedException {
|
||||||
String content = event.getReadableMessageContent();
|
String content = event.getReadableMessageContent();
|
||||||
var userId = event.getMessageAuthor().getId();
|
var userId = event.getMessageAuthor().getId();
|
||||||
var systemMessage = this.userDBService.getConfig(String.valueOf(userId)).get().systemMessage();
|
var systemMessage = this.userDBService.getConfig(String.valueOf(userId)).get().systemMessage();
|
||||||
var request = new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
|
var request = new ChatGPTRequestBuilder().simpleRequest(content, systemMessage);
|
||||||
try {
|
|
||||||
var response = this.chatGPTService.submit(request);
|
var response = this.chatGPTService.submit(request);
|
||||||
if (response.choices().size() < 1) {
|
if (response.choices().size() < 1) {
|
||||||
event.getChannel().sendMessage("No response available");
|
event.getMessage().reply("No response available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var answer = response.choices().get(0).message().content();
|
var answer = response.choices().get(0).message().content();
|
||||||
logUserMessage(event, content, answer, response.usage().totalTokens());
|
logUserMessage(event, content, answer, response.usage().totalTokens());
|
||||||
event.getMessage().reply(answer);
|
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
|
@Override
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public class ChatGPTService {
|
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 ObjectMapper mapper = new ObjectMapper();
|
||||||
private final String apiKey;
|
private final String apiKey;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
@ -29,15 +29,14 @@ public class ChatGPTService {
|
||||||
.POST(HttpRequest.BodyPublishers.ofByteArray(data))
|
.POST(HttpRequest.BodyPublishers.ofByteArray(data))
|
||||||
.setHeader("Content-Type", "application/json")
|
.setHeader("Content-Type", "application/json")
|
||||||
.setHeader("Authorization", "Bearer " + this.apiKey)
|
.setHeader("Authorization", "Bearer " + this.apiKey)
|
||||||
.timeout(Duration.ofSeconds(30))
|
.timeout(Duration.ofSeconds(90))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
var responseStream = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
var responseStream = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||||
if (responseStream.statusCode() != 200) {
|
if (responseStream.statusCode() != 200) {
|
||||||
throw new ResponseException("Response status code was not 200: " + responseStream.statusCode());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,46 +3,49 @@ version: "3"
|
||||||
services:
|
services:
|
||||||
bot:
|
bot:
|
||||||
image: git.hhhammer.de/hamburghammer/dchat/bot:latest
|
image: git.hhhammer.de/hamburghammer/dchat/bot:latest
|
||||||
build: ./bot/
|
build:
|
||||||
|
dockerfile: Containerfile
|
||||||
|
context: .
|
||||||
|
target: bot
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- migration
|
||||||
environment:
|
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"
|
- JDK_JAVA_OPTIONS="--enable-preview"
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: git.hhhammer.de/hamburghammer/dchat/web:latest
|
image: git.hhhammer.de/hamburghammer/dchat/web:latest
|
||||||
build: ./web/
|
build:
|
||||||
|
dockerfile: Containerfile
|
||||||
|
context: .
|
||||||
|
target: web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- migration
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=<postgres-user>
|
|
||||||
- POSTGRES_PASSWORD=<postgres-password>
|
|
||||||
- POSTGRES_URL=jdbc:postgresql://db:5432/dchat
|
|
||||||
- JDK_JAVA_OPTIONS="--enable-preview"
|
- JDK_JAVA_OPTIONS="--enable-preview"
|
||||||
|
|
||||||
migration:
|
migration:
|
||||||
image: git.hhhammer.de/hamburghammer/dchat/migration:latest
|
image: git.hhhammer.de/hamburghammer/dchat/migration:latest
|
||||||
build: ./migration/
|
build:
|
||||||
|
dockerfile: Containerfile
|
||||||
|
context: .
|
||||||
|
target: migration
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=<postgres-user>
|
|
||||||
- POSTGRES_PASSWORD=<postgres-password>
|
|
||||||
- POSTGRES_URL=jdbc:postgresql://db:5432/dchat
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: docker.io/postgres:15-alpine
|
image: docker.io/postgres:15-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
|
||||||
- POSTGRES_USER=<postgres-user>
|
|
||||||
- POSTGRES_PASSWORD=<postgres-password>
|
|
||||||
- POSTGRES_DB=dchat
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/postgres:/var/lib/postgresql/data:rw
|
- ./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
|
||||||
|
|
Loading…
Reference in a new issue